Setup

workflow_name <- "netnav_06_model_comparison"

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(here)
## here() starts at /Users/jaeyoungson/Documents/GitHub/network-navigation-replay
library(patchwork)

library(glmmTMB)
## Warning in checkDepPackageVersion(dep_pkg = "TMB"): Package version inconsistency detected.
## glmmTMB was built with TMB version 1.9.6
## Current TMB version is 1.9.10
## Please re-install glmmTMB from source or restore original 'TMB' package (see '?reinstalling' for more information)
library(broom.mixed)

source(here("code", "utils", "modeling_utils.R"))
source(here("code", "utils", "representation_utils.R"))
source(here("code", "utils", "bayesian_model_selection.R"))

source(here("code", "utils", "ggplot_themes.R"))
source(here("code", "utils", "kable_utils.R"))
## 
## Attaching package: 'kableExtra'
## 
## The following object is masked from 'package:dplyr':
## 
##     group_rows
source(here("code", "utils", "unicode_greek.R"))

knitting <- knitr::is_html_output()

create_path <- function(this_path) {
  if (!dir.exists(this_path)) {
    dir.create(this_path, recursive = TRUE)
  }
}

predict_glmmTMB <- function(make_predictions_for, model_object) {
  make_predictions_for %>%
    bind_cols(
      predict(
        object = model_object,
        newdata = .,
        re.form = NA, allow.new.levels = TRUE, se.fit = TRUE, type = "response"
      )
    )
}

if (knitting) {
  here("outputs", workflow_name) %>%
    create_path()
  
  here("figures") %>%
    create_path()
}
nav_study1 <- here("data", "clean_data", "study1_message_passing.csv") %>%
  read_csv(show_col_types = FALSE) %>%
  filter(
    # two_correct_options == FALSE,
    shortest_path_given_opts == shortest_path_given_start_end
  ) %>%
  mutate(
    study = "Study 1",
    measurement_id = str_c("D", measurement_id),
    shortest_path = factor(shortest_path_given_opts)
  ) %>%
  select(
    study, sub_id, measurement_id, shortest_path,
    startpoint_id, endpoint_id,
    opt1_id, opt2_id,
    correct_choice, sub_choice,
    correct, rt,
    two_correct_options,
    opt1_distance = dist_opt1,
    opt2_distance = dist_opt2
  )

nav_study2 <- here("data", "clean_data", "study2_message_passing.csv") %>%
  read_csv(show_col_types = FALSE) %>%
  filter(
    # two_correct_options == FALSE,
    shortest_path_given_opts == shortest_path_given_start_end
  ) %>%
  mutate(
    study = "Study 2",
    measurement_id = case_when(
      network == "learned" ~ str_c("D", measurement_id),
      network == "reevaluated" ~ "D2b"
    ),
    shortest_path = factor(shortest_path_given_opts)
  ) %>%
  select(
    study, sub_id, measurement_id, shortest_path,
    startpoint_id, endpoint_id,
    opt1_id, opt2_id,
    correct_choice, sub_choice,
    correct, rt,
    two_correct_options,
    opt1_distance = dist_opt1,
    opt2_distance = dist_opt2
  )

nav_study3 <- here("data", "clean_data", "study3_message_passing.csv") %>%
  read_csv(show_col_types = FALSE) %>%
  filter(
    # two_correct_options == FALSE,
    shortest_path_given_opts == shortest_path_given_start_end
  ) %>%
  mutate(
    study = "Study 3",
    measurement_id = case_when(
      network == "reevaluated" ~ "D2b",
      measurement_id == 1 ~ "D1",
      measurement_id == 2 ~ "D1b",
      measurement_id == 3 ~ "D2"
    ),
    shortest_path = factor(shortest_path_given_opts)
  ) %>%
  select(
    study, sub_id, measurement_id, shortest_path,
    startpoint_id, endpoint_id,
    opt1_id, opt2_id,
    correct_choice, sub_choice,
    correct, rt,
    two_correct_options,
    opt1_distance = dist_opt1,
    opt2_distance = dist_opt2
  )
bfs_backward_sims <- here(
  "data", "bfs_sims", "bfs_sims_learned_backward.csv"
) %>%
  read_csv(show_col_types = FALSE) %>%
  filter(
    shortest_path_given_opts == shortest_path_given_start_end,
    # two_correct_options == FALSE
  ) %>%
  mutate(shortest_path = factor(shortest_path_given_opts)) %>%
  select(-starts_with("shortest_path_given")) %>%
  group_by(
    shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
    correct_choice, two_correct_options
  ) %>%
  summarise(
    p_bfs_correct = mean(bfs_choice == correct_choice),
    p_bfs_chooses_opt1 = mean(bfs_choice == opt1_id),
    bfs_visits = mean(bfs_n_visits_total),
    .groups = "drop"
  )

bfs_forward_sims <- here("data", "bfs_sims", "bfs_sims_learned_forward.csv") %>%
  read_csv(show_col_types = FALSE) %>%
  filter(
    shortest_path_given_opts == shortest_path_given_start_end,
    # two_correct_options == FALSE
  ) %>%
  mutate(shortest_path = factor(shortest_path_given_opts)) %>%
  select(-starts_with("shortest_path_given")) %>%
  group_by(
    shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
    correct_choice, two_correct_options
  ) %>%
  summarise(
    p_bfs_correct = mean(bfs_choice == correct_choice),
    p_bfs_chooses_opt1 = mean(bfs_choice == opt1_id),
    bfs_visits = mean(bfs_n_visits_total),
    .groups = "drop"
  )

nav_trials <- here("data", "clean_data", "study1_message_passing.csv") %>%
  read_csv(show_col_types = FALSE) %>%
  filter(
    # two_correct_options == FALSE,
    shortest_path_given_opts == shortest_path_given_start_end
  ) %>%
  mutate(shortest_path = factor(shortest_path_given_opts)) %>%
  filter(sub_id == 1) %>%
  select(
    shortest_path,
    startpoint_id, endpoint_id,
    opt1_id, opt2_id,
    correct_choice,
    opt1_distance = dist_opt1,
    opt2_distance = dist_opt2,
    two_correct_options
  ) %>%
  arrange(shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id) %>%
  # Replace undefined distances (corresponding to impossible options)
  # so that the softmax gets non-NA inputs; we assume that impossible
  # options are just as bad as the longest distance found in this set
  # of trials, i.e., a distance of 8
  mutate(across(c(opt1_distance, opt2_distance), ~replace_na(.x, 8)))

adjlist <- here("data", "clean_data", "adjlist_learned.csv") %>%
  read_csv(show_col_types = FALSE)

transmat <- adjlist %>%
  group_by(from) %>%
  mutate(edge = edge / sum(edge)) %>%
  ungroup() %>%
  pivot_wider(names_from = to, values_from = edge) %>%
  column_to_rownames("from") %>%
  as.matrix()
load_params_from_scratch <- FALSE

if (load_params_from_scratch == TRUE) {
  params <- here("data", "param_fits") %>%
    fs::dir_ls(
      recurse = 1,
      regexp = str_c(
        "_",
        "(bfs_(backward|forward)|",
        "ideal_obs|",
        "sr_(analytic|delta_rule))_",
        "(.)+\\.csv"
      )
    ) %>%
    map_dfr(
      .f = ~read_csv(.x, show_col_types = FALSE),
      .id = "filename"
    ) %>%
    mutate(
      # Recover model ID
      model = str_extract(
        filename,
        str_c(
          "_",
          "(bfs_(backward|forward)|",
          "ideal_obs|",
          "sr_(analytic|delta_rule))_"
        )
      ),
      model = str_sub(model, 2, -2),
      # Recover study ID
      study = str_extract(filename, "study[[:digit:]]"),
      study = str_replace(study, "study", "Study "),
      # Recover subject ID
      sub_id = str_extract(filename, "sub_[[:digit:]]+"),
      sub_id = str_remove(sub_id, "sub_"),
      sub_id = as.integer(sub_id),
      # Recover measurement ID
      measurement_id = str_extract(filename, "_D[[:digit:]]b?"),
      measurement_id = str_remove(measurement_id, "_"),
      # Get parameter values
      param_value = if_else(
        is.na(param_value_human_readable),
        param_value,
        param_value_human_readable
      )
    ) %>%
    # Find best-fitting optimization run
    filter(convergence == "converged") %>%
    group_by(model, study, sub_id, measurement_id) %>%
    slice_min(optim_value, n = 1) %>%
    ungroup() %>%
    # Some subjects may have had multiple "best" optimization runs
    # In that case, just go with whichever "best" run was estimated first
    group_by(model, study, sub_id, measurement_id) %>%
    slice_min(optimizer_run, n = 1) %>%
    ungroup() %>%
    # Clean up
    select(
      model, study, sub_id, measurement_id,
      param_name, param_value,
      neg_loglik = optim_value
    ) %>%
    arrange(model, study, sub_id, measurement_id, param_name)
  
  here("data", "param_fits", "clean_params") %>%
    create_path()
  
  params %>%
    write_csv(
      file = here("data", "param_fits", "clean_params", "clean_param_fits.csv")
    )
}

params <- here("data", "param_fits", "clean_params", "clean_param_fits.csv") %>%
  read_csv(show_col_types = FALSE)

AICc

As our metric of log-evidence, we’ll use AICc, i.e., AIC corrected for a relatively small N.

aicc <- params %>%
  select(study, sub_id, measurement_id, model, neg_loglik) %>%
  distinct() %>%
  arrange(study, sub_id, measurement_id) %>%
  mutate(
    n_params = if_else(model == "sr", 2, 1),
    n_datapoints = 115,
    aic = (-2 * -neg_loglik) + (2 * n_params),
    aicc = aic + (
      (2 * n_params * (n_params + 1)) / (n_datapoints - n_params - 1)
    )
  )

SR analytic vs delta-rule

Before we go any further, let’s just get one targeted comparison out of the way. In earlier scripts, we saw that SR matrices can be constructed using a closed-form analytic solution, or a delta-rule updating mechanism. These different implementations can, in principle, end up making very different predictions. Here, we’ll see whether there’s evidence that one implementation fits better than the other.

Below, we’re directly comparing the AICc of the analytic vs delta-rule implementations. Each datapoint is one subject, and the lines connect a subject’s AICc from one implementation to the other. The red bars reflect the means. The lines are remarkably flat, indicating that there is functionally no real difference in the model goodness-of-fit.

plot_sr_comparison_aicc <- aicc %>%
  filter(str_detect(model, "sr_")) %>%
  mutate(facet_label = str_c(study, ", ", measurement_id)) %>%
  ggplot(aes(x=model, y=aicc)) +
  theme_custom() +
  facet_wrap(~facet_label, scales = "free_x") +
  geom_point(alpha = 0.5) +
  geom_line(aes(group = sub_id), alpha = 0.2) +
  stat_summary(geom = "crossbar", fun = mean, color = "red") +
  scale_x_discrete(
    name = "SR implementation",
    labels = c("sr_analytic"="Analytic", "sr_delta_rule"="Delta-rule")
  ) +
  ylab("AICc") +
  ggtitle("AICc comparison of SR implementations")

plot_sr_comparison_aicc

if (knitting) {
  ggsave(
    filename = here(
      "outputs", workflow_name,
      "sr_aicc_analytic_vs_delta_rule.pdf"
    ),
    plot = plot_sr_comparison_aicc,
    width = 6, height = 4,
    units = "in", dpi = 300
  )
}

We’re not interested in doing any sort of hypothesis testing here, but for the purpose of doing model selection, we do want to know whether choosing one implementation over the other might result in forming different conclusions about the parameter fits.

Below, the bars reflect medians. We can see that, by-and-large, the two implementations result in very similar estimates.

plot_sr_comparison_gamma <- params %>%
  filter(str_detect(model, "sr_")) %>%
  filter(param_name == "sr_gamma") %>%
  select(model, study, sub_id, measurement_id, sr_gamma = param_value) %>%
  ggplot(aes(x=measurement_id, y=sr_gamma, color=model)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x") +
  geom_point(
    alpha = 0.5, position = position_dodge(width = 0.25), show.legend = FALSE
  ) +
  geom_line(aes(group = interaction(model, sub_id)), alpha = 0.2) +
  stat_summary(geom = "crossbar", fun = median) +
  xlab("Measurement") +
  ylab("SR gamma") +
  scale_color_manual(
    name = "SR implementation",
    labels = c("sr_analytic"="Analytic", "sr_delta_rule"="Delta-rule"),
    values = c("sr_analytic"="#ca0020", "sr_delta_rule"="#0571b0")
  ) +
  ggtitle("Estimates of SR gamma: Analytic vs delta-rule") +
  theme(legend.position = "bottom")

plot_sr_comparison_gamma
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?

if (knitting) {
  ggsave(
    filename = here(
      "outputs", workflow_name,
      "sr_gamma_analytic_vs_delta_rule.pdf"
    ),
    plot = plot_sr_comparison_gamma,
    width = 6, height = 4,
    units = "in", dpi = 300
  )
}
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?

In the modeling, the goal was to test changes in the gamma parameter, assuming an asymptotic representation. These results suggest that there is nothing lost by using the analytic closed-form implementation, so we’ll stick with that from here onwards.

Description of parameter fits

params %>%
  group_by(model, study, measurement_id, param_name) %>%
  summarise(
    param_mean = mean(param_value),
    param_median = median(param_value),
    .groups = "drop"
  ) %>%
  kable_custom(
    captions = "Descriptive stats: parameter fits",
    grouping_var = model
  )
Descriptive stats: parameter fits
study measurement_id param_name param_mean param_median
bfs_backward
Study 1 D1 search_threshold 3.834 3.659
Study 2 D1 search_threshold 4.769 4.431
Study 2 D2 search_threshold 5.319 4.762
Study 3 D1 search_threshold 5.072 4.535
Study 3 D1b search_threshold 4.870 4.144
Study 3 D2 search_threshold 5.763 4.652
bfs_forward
Study 1 D1 search_threshold 5.926 6.038
Study 2 D1 search_threshold 7.438 6.854
Study 2 D2 search_threshold 8.116 7.168
Study 3 D1 search_threshold 7.783 6.625
Study 3 D1b search_threshold 7.611 6.606
Study 3 D2 search_threshold 8.618 7.404
ideal_obs
Study 1 D1 softmax_temperature -0.272 -0.184
Study 2 D1 softmax_temperature -0.475 -0.289
Study 2 D2 softmax_temperature -0.685 -0.353
Study 3 D1 softmax_temperature -0.687 -0.284
Study 3 D1b softmax_temperature -0.696 -0.319
Study 3 D2 softmax_temperature -1.024 -0.380
sr_analytic
Study 1 D1 softmax_temperature 1797.497 26.023
Study 1 D1 sr_gamma 0.567 0.647
Study 2 D1 softmax_temperature 285.337 24.286
Study 2 D1 sr_gamma 0.659 0.722
Study 2 D2 softmax_temperature 560.735 30.060
Study 2 D2 sr_gamma 0.722 0.826
Study 3 D1 softmax_temperature 688.291 31.783
Study 3 D1 sr_gamma 0.690 0.816
Study 3 D1b softmax_temperature 761.781 40.193
Study 3 D1b sr_gamma 0.673 0.825
Study 3 D2 softmax_temperature 2041.139 54.912
Study 3 D2 sr_gamma 0.718 0.834
sr_delta_rule
Study 1 D1 softmax_temperature 45.047 12.202
Study 1 D1 sr_gamma 0.557 0.639
Study 2 D1 softmax_temperature 32.039 13.713
Study 2 D1 sr_gamma 0.649 0.709
Study 2 D2 softmax_temperature 119.106 21.976
Study 2 D2 sr_gamma 0.714 0.819
Study 3 D1 softmax_temperature 105.173 18.794
Study 3 D1 sr_gamma 0.680 0.790
Study 3 D1b softmax_temperature 40.854 15.469
Study 3 D1b sr_gamma 0.667 0.813
Study 3 D2 softmax_temperature 278.951 24.712
Study 3 D2 sr_gamma 0.716 0.835
plot_params_bfs_backward <- params %>%
  filter(model == "bfs_backward") %>%
  pivot_wider(names_from = param_name, values_from = param_value) %>%
  ggplot(aes(x=measurement_id, y=search_threshold)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x") +
  stat_summary(geom = "crossbar", fun = mean, color = "red") +
  stat_summary(geom = "crossbar", fun = median, color = "blue") +
  geom_point(
    alpha = 0.1,
    position = position_jitter(width = 0.1, height = 0, seed = 1)
  ) +
  geom_line(
    aes(group = sub_id), alpha = 0.25,
    position = position_jitter(width = 0.1, height = 0, seed = 1)
  ) +
  xlab("Measurement") +
  ylab("Search threshold") +
  ggtitle("BFS-backward")

plot_params_bfs_forward <- params %>%
  filter(model == "bfs_forward") %>%
  pivot_wider(names_from = param_name, values_from = param_value) %>%
  ggplot(aes(x=measurement_id, y=search_threshold)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x") +
  stat_summary(geom = "crossbar", fun = mean, color = "red") +
  stat_summary(geom = "crossbar", fun = median, color = "blue") +
  geom_point(
    alpha = 0.1,
    position = position_jitter(width = 0.1, height = 0, seed = 1)
  ) +
  geom_line(
    aes(group = sub_id), alpha = 0.25,
    position = position_jitter(width = 0.1, height = 0, seed = 1)
  ) +
  xlab("Measurement") +
  ylab("Search threshold") +
  ggtitle("BFS-forward")

plot_params_ideal_obs <- params %>%
  filter(model == "ideal_obs") %>%
  pivot_wider(names_from = param_name, values_from = param_value) %>%
  ggplot(aes(x=measurement_id, y=softmax_temperature)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x") +
  stat_summary(geom = "crossbar", fun = mean, color = "red") +
  stat_summary(geom = "crossbar", fun = median, color = "blue") +
  geom_point(
    alpha = 0.1,
    position = position_jitter(width = 0.1, height = 0, seed = 1)
  ) +
  geom_line(
    aes(group = sub_id), alpha = 0.25,
    position = position_jitter(width = 0.1, height = 0, seed = 1)
  ) +
  xlab("Measurement") +
  ylab("Inverse temperature") +
  ggtitle("Ideal observer")

plot_params_sr_temp <- params %>%
  filter(model == "sr_analytic") %>%
  pivot_wider(names_from = param_name, values_from = param_value) %>%
  ggplot(aes(x=measurement_id, y=softmax_temperature)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x") +
  stat_summary(geom = "crossbar", fun = mean, color = "red") +
  stat_summary(geom = "crossbar", fun = median, color = "blue") +
  geom_point(
    alpha = 0.1,
    position = position_jitter(width = 0.1, height = 0, seed = 1)
  ) +
  geom_line(
    aes(group = sub_id), alpha = 0.25,
    position = position_jitter(width = 0.1, height = 0, seed = 1)
  ) +
  xlab("Measurement") +
  ylab("Inverse temperature") +
  ggtitle("Successor Representation") +
  coord_cartesian(ylim = c(0, 3000))

plot_params_sr_gamma <- params %>%
  filter(model == "sr_analytic") %>%
  pivot_wider(names_from = param_name, values_from = param_value) %>%
  ggplot(aes(x=measurement_id, y=sr_gamma)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x") +
  stat_summary(geom = "crossbar", fun = mean, color = "red") +
  stat_summary(geom = "crossbar", fun = median, color = "blue") +
  geom_point(
    alpha = 0.1,
    position = position_jitter(width = 0.1, height = 0, seed = 1)
  ) +
  geom_line(
    aes(group = sub_id), alpha = 0.25,
    position = position_jitter(width = 0.1, height = 0, seed = 1)
  ) +
  xlab("Measurement") +
  ylab("Gamma") +
  ggtitle("Successor Representation")

plot_params_all <- (
  (plot_params_bfs_backward | plot_params_bfs_forward | plot_params_ideal_obs) /
    (plot_params_sr_temp | plot_params_sr_gamma)
) +
  plot_annotation(
    title = "Estimated parameters",
    tag_levels = "A", tag_suffix = ".",
    theme = theme(plot.title = element_text(hjust = 0.5))
  )

plot_params_all
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "param_estimates.pdf"),
    plot = plot_params_all,
    width = 12, height = 6,
    units = "in", dpi = 300
  )
}
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?

Akaike weights

We’ll later use protected exceedance probabilities (PXP) to do formal inference to test whether a particular model provides a significantly better group-level fit than other models. But first, we do want to acknowledge that there’s likely to be some individual differences in how well a particular model fits each subject. To get a sense for this, we’ll use Akaike weights, which provide the probability that a particular model is the “best” given the data and the set of candidate models.

akaike_weights <- aicc %>%
  filter(model != "sr_delta_rule") %>%
  group_by(study, measurement_id, sub_id) %>%
  mutate(
    relative_likelihood = exp(-1/2 * (aicc - min(aicc))),
    akaike_weight = relative_likelihood / sum(relative_likelihood),
    evidence_ratio = max(akaike_weight) / akaike_weight
  ) %>%
  ungroup() %>%
  arrange(study, measurement_id, sub_id, evidence_ratio)

We can first average over all subjects’ Akaike weights to get a sense for what the “best-fitting” model is across subjects. This suggests that the SR consistently comes out on top, followed pretty consistently by BFS-backward.

plot_akaike_group <- akaike_weights %>%
  group_by(study, measurement_id, model) %>%
  summarise(akaike_weight = mean(akaike_weight), .groups = "drop") %>%
  mutate(text = round(akaike_weight, 2)) %>%
  ggplot(aes(x=measurement_id, y=akaike_weight, fill=model)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x") +
  geom_col() +
  geom_text(aes(label = text), position = position_stack(vjust = 0.5)) +
  scale_x_discrete(
    name = NULL,
    labels = c(
      "D1"="Before\nrest",
      "D2"="After\novernight\nrest",
      "D1b"="After\nawake\nrest"
    )
  ) +
  scale_y_continuous(
    name = NULL,
    expand = expansion(mult = c(0.01, 0.01))
  ) +
  scale_fill_manual(
    name = NULL,
    labels = c(
      "bfs_backward" = "BFS-backward",
      "bfs_forward" = "BFS-forward",
      "ideal_obs" = "Ideal observer",
      "sr_analytic" = "Successor Representation"
    ),
    values = c(
      "bfs_backward" = "#a6dba0",
      "bfs_forward" = "#5aae61",
      "ideal_obs" = "#1b7837",
      "sr_analytic" = "#af8dc3"
    )
  ) +
  theme(
    legend.position = "bottom",
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank()
  ) +
  ggtitle("Akaike weights")

plot_akaike_group

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "akaike_weights_group.pdf"),
    plot = plot_akaike_group,
    width = 6, height = 4,
    units = "in", dpi = 300
  )
}

We can break this out and plot each individual subject’s Akaike weights.

plot_akaike_individual <- akaike_weights %>%
  mutate(
    sub_id = factor(sub_id),
    measurement_id = case_when(
      measurement_id == "D1" ~ "before rest",
      measurement_id == "D1b" ~ "after awake rest",
      measurement_id == "D2" ~ "after overnight rest"
    ),
    study = str_c(study, ", ", measurement_id),
    study = fct_relevel(
      study,
      "Study 1, before rest",
      "Study 2, before rest",
      "Study 2, after overnight rest",
      "Study 3, before rest",
      "Study 3, after awake rest",
      "Study 3, after overnight rest"
    )
  ) %>%
  ggplot(aes(x=sub_id, y=akaike_weight, fill=model)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x", ncol = 1) +
  geom_col() +
  scale_x_discrete(name = "Subject ID") +
  scale_y_continuous(
    name = NULL,
    expand = expansion(mult = c(0.01, 0.01))
  ) +
  scale_fill_manual(
    name = NULL,
    labels = c(
      "bfs_backward" = "BFS-backward",
      "bfs_forward" = "BFS-forward",
      "ideal_obs" = "Ideal observer",
      "sr_analytic" = "Successor Representation"
    ),
    values = c(
      "bfs_backward" = "#a6dba0",
      "bfs_forward" = "#5aae61",
      "ideal_obs" = "#1b7837",
      "sr_analytic" = "#af8dc3"
    )
  ) +
  theme(
    legend.position = "bottom",
    axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank()
  ) +
  ggtitle("Akaike weights")

plot_akaike_individual

if (knitting) {
  ggsave(
    filename = here(
      "outputs", workflow_name,
      "akaike_weights_per_subject.pdf"
    ),
    plot = plot_akaike_individual,
    width = 8, height = 10,
    units = "in", dpi = 300
  )
}

Akaike weights provide a nice goodness-of-fit metric that respects the probabilistic aspect of model comparison, and can do so at both the group- and individual-level. However, model selection requires us to ultimately make a discrete choice. If we made per-subject decisions based simply by choosing the single best-fitting model, what proportion of subjects are best-fit by each model? We see that the pattern of results basically mirrors what we’d seen in the Akaike weights, such that the SR is the best-fitting model for the majority of subjects, followed by BFS-backward.

best_fitting_model_per_sub <- akaike_weights %>%
  group_by(study, measurement_id, sub_id) %>%
  slice_max(akaike_weight) %>%
  ungroup() %>%
  select(study, measurement_id, sub_id, best_fitting_model = model)

plot_best_fitting_model_prop <- best_fitting_model_per_sub %>%
  count(study, measurement_id, best_fitting_model) %>%
  group_by(study, measurement_id) %>%
  mutate(
    p = n / sum(n),
    text = str_c(round(p, 2) * 100, "%")
  ) %>%
  ggplot(aes(x=measurement_id, y=p, fill=best_fitting_model)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x") +
  geom_col() +
  geom_text(aes(label = text), position = position_stack(vjust = 0.5)) +
  scale_x_discrete(
    name = NULL,
    labels = c(
      "D1"="Before\nrest",
      "D2"="After\novernight\nrest",
      "D1b"="After\nawake\nrest"
    )
  ) +
  scale_y_continuous(
    name = NULL,
    expand = expansion(mult = c(0.01, 0.01))
  ) +
  scale_fill_manual(
    name = NULL,
    labels = c(
      "bfs_backward" = "BFS-backward",
      "bfs_forward" = "BFS-forward",
      "ideal_obs" = "Ideal observer",
      "sr_analytic" = "Successor Representation"
    ),
    values = c(
      "bfs_backward" = "#a6dba0",
      "bfs_forward" = "#5aae61",
      "ideal_obs" = "#1b7837",
      "sr_analytic" = "#af8dc3"
    )
  ) +
  theme(
    legend.position = "bottom",
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank()
  ) +
  ggtitle("Proportion of subjects best fit by each model")

plot_best_fitting_model_prop

if (knitting) {
  ggsave(
    filename = here(
      "outputs", workflow_name,
      "prop_subjects_best_fit.pdf"
    ),
    plot = plot_best_fitting_model_prop,
    width = 6, height = 4,
    units = "in", dpi = 300
  )
}

Protected exceedance probabilities

Protected exceedance probabilities provide a formal test of a model’s group-level fit compared to other candidate models. To run this analysis, we’ll adapt software originally written by Matteo Lisi (https://github.com/mattelisi/bmsR). We’ll use AICc as our metric of log-evidence.

pxp_results <- aicc %>%
  filter(model != "sr_delta_rule") %>%
  # In PXP, more is more. AICc, in contrast, is based off neg-LL, and so is
  # interpreted as "smaller is better". So, do a sign flip.
  mutate(aicc = -aicc) %>%
  select(study, measurement_id, sub_id, model, aicc) %>%
  # Compute PXP for each study/measurement
  pivot_wider(names_from = model, values_from = aicc) %>%
  select(-sub_id) %>%
  group_by(study, measurement_id) %>%
  nest() %>%
  mutate(
    test = map(
      .x = data,
      .f = ~bayesian_model_selection(.x)
    )
  ) %>%
  ungroup() %>%
  unnest(test) %>%
  select(-data)

In the results, it’s clear that the SR comes out on top, and by a large margin.

pxp_results %>%
  mutate(
    measurement_id = case_when(
      measurement_id == "D1" ~ "before rest",
      measurement_id == "D1b" ~ "after awake rest",
      measurement_id == "D2" ~ "after overnight rest"
    ),
    study = str_c(study, ", ", measurement_id),
    study = fct_relevel(
      study,
      "Study 1, before rest",
      "Study 2, before rest",
      "Study 2, after overnight rest",
      "Study 3, before rest",
      "Study 3, after awake rest",
      "Study 3, after overnight rest"
    )
  ) %>%
  select(-measurement_id) %>%
  arrange(study, desc(pxp)) %>%
  kable_custom("PXP results", grouping_var = study)
PXP results
model alpha expected_model_frequencies omnibus_risk xp pxp
Study 1, before rest
sr_analytic 36.549 0.677 0.000 0.999459 9.994587e-01
bfs_backward 14.010 0.259 0.000 0.000541 5.410882e-04
bfs_forward 1.475 0.027 0.000 0.000000 8.835920e-08
ideal_obs 1.967 0.036 0.000 0.000000 8.835920e-08
Study 2, before rest
sr_analytic 28.381 0.526 0.011 0.983780 9.758328e-01
bfs_backward 14.577 0.270 0.011 0.015947 1.848192e-02
ideal_obs 8.476 0.157 0.011 0.000273 2.977680e-03
bfs_forward 2.566 0.048 0.011 0.000000 2.707637e-03
Study 2, after overnight rest
sr_analytic 32.103 0.595 0.000 0.998161 9.981347e-01
bfs_backward 13.044 0.242 0.000 0.001818 1.826722e-03
ideal_obs 7.667 0.142 0.000 0.000021 2.978537e-05
bfs_forward 1.186 0.022 0.000 0.000000 8.786111e-06
Study 3, before rest
sr_analytic 26.939 0.539 0.005 0.976547 9.726377e-01
bfs_backward 14.387 0.288 0.005 0.023440 2.465903e-02
ideal_obs 5.363 0.107 0.005 0.000012 1.357085e-03
bfs_forward 3.312 0.066 0.005 0.000001 1.346145e-03
Study 3, after awake rest
sr_analytic 32.674 0.653 0.001 0.999898 9.994390e-01
ideal_obs 9.419 0.188 0.001 0.000102 2.549721e-04
bfs_backward 5.342 0.107 0.001 0.000000 1.530346e-04
bfs_forward 2.565 0.051 0.001 0.000000 1.530346e-04
Study 3, after overnight rest
sr_analytic 30.775 0.615 0.001 0.999712 9.988405e-01
ideal_obs 9.425 0.188 0.001 0.000237 5.273355e-04
bfs_backward 7.940 0.159 0.001 0.000051 3.415517e-04
bfs_forward 1.861 0.037 0.001 0.000000 2.906110e-04
plot_pxp <- pxp_results %>%
  mutate(
    text = round(pxp, 2) * 100,
    text = if_else(model != "sr_analytic", "", str_c(text, "%"))
  ) %>%
  ggplot(aes(x=measurement_id, y=pxp, fill=model)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x") +
  geom_col() +
  geom_text(aes(label = text), position = position_stack(vjust = 0.5)) +
  scale_x_discrete(
    name = NULL,
    labels = c(
      "D1"="Before\nrest",
      "D2"="After\novernight\nrest",
      "D1b"="After\nawake\nrest"
    )
  ) +
  scale_y_continuous(
    name = NULL,
    expand = expansion(mult = c(0.01, 0.01))
  ) +
  scale_fill_manual(
    name = NULL,
    labels = c(
      "bfs_backward" = "BFS-backward",
      "bfs_forward" = "BFS-forward",
      "ideal_obs" = "Ideal observer",
      "sr_analytic" = "Successor Representation"
    ),
    values = c(
      "bfs_backward" = "#a6dba0",
      "bfs_forward" = "#5aae61",
      "ideal_obs" = "#1b7837",
      "sr_analytic" = "#af8dc3"
    )
  ) +
  theme(
    legend.position = "bottom",
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank()
  ) +
  ggtitle("Protected exceedance probabilities")

plot_pxp

if (knitting) {
  ggsave(
    filename = here(
      "outputs", workflow_name,
      "pxp.pdf"
    ),
    plot = plot_pxp,
    width = 6, height = 4,
    units = "in", dpi = 300
  )
}

Posterior predictive check

Simulate predicted behaviors

It’s nice to see consistency in the quantitative model comparison and selection, but we’d also like to see how well human behaviors are qualitatively described by our models. To do this, we’ll simulate subjects’ predicted behaviors given their model parameters.

ppc_bfs_backward <- expand_grid(
  # List of all trials from BFS simulation for each subject/measurement
  params %>%
    select(study, sub_id, measurement_id) %>%
    distinct(),
  bfs_backward_sims
) %>%
  # Add subject-specific parameters
  left_join(
    params %>%
      filter(model == "bfs_backward") %>%
      pivot_wider(names_from = param_name, values_from = param_value) %>%
      select(study, sub_id, measurement_id, search_threshold),
    by = join_by(study, sub_id, measurement_id)
  ) %>%
  # What's the probability of *completing* BFS-online all the way through?
  rowwise() %>%
  mutate(
    p_complete_bfs = softmax(
      option_values = c(search_threshold, bfs_visits),
      option_chosen = 1,
      temperature = 1
    )
  ) %>%
  ungroup() %>%
  # Weigh BFS predictions accordingly
  mutate(
    p_give_up = 1 - p_complete_bfs,
    model_p_correct = (
      (p_complete_bfs * p_bfs_correct) + (p_give_up * 1/2)
    )
  ) %>%
  # Add subjects' actual choices
  left_join(
    bind_rows(nav_study1, nav_study2, nav_study3) %>%
      select(
        study, sub_id, measurement_id,
        shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
        sub_choice, sub_correct = correct, sub_rt = rt
      ),
    by = join_by(
      study, sub_id, measurement_id,
      shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
    )
  )
ppc_bfs_forward <- expand_grid(
  # List of all trials from BFS simulation for each subject/measurement
  params %>%
    select(study, sub_id, measurement_id) %>%
    distinct(),
  bfs_forward_sims
) %>%
  # Add subject-specific parameters
  left_join(
    params %>%
      filter(model == "bfs_forward") %>%
      pivot_wider(names_from = param_name, values_from = param_value) %>%
      select(study, sub_id, measurement_id, search_threshold),
    by = join_by(study, sub_id, measurement_id)
  ) %>%
  # What's the probability of *completing* BFS-online all the way through?
  rowwise() %>%
  mutate(
    p_complete_bfs = softmax(
      option_values = c(search_threshold, bfs_visits),
      option_chosen = 1,
      temperature = 1
    )
  ) %>%
  ungroup() %>%
  # Weigh BFS predictions accordingly
  mutate(
    p_give_up = 1 - p_complete_bfs,
    model_p_correct = (
      (p_complete_bfs * p_bfs_correct) + (p_give_up * 1/2)
    )
  ) %>%
  # Add subjects' actual choices
  left_join(
    bind_rows(nav_study1, nav_study2, nav_study3) %>%
      select(
        study, sub_id, measurement_id,
        shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
        sub_choice, sub_correct = correct, sub_rt = rt
      ),
    by = join_by(
      study, sub_id, measurement_id,
      shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
    )
  )
ppc_ideal_obs <- expand_grid(
  # List of all trials for each subject/measurement
  params %>%
    select(study, sub_id, measurement_id) %>%
    distinct(),
  nav_trials
) %>%
  # Add subject-specific parameters
  left_join(
    params %>%
      filter(model == "ideal_obs") %>%
      pivot_wider(names_from = param_name, values_from = param_value) %>%
      select(study, sub_id, measurement_id, softmax_temperature),
    by = join_by(study, sub_id, measurement_id)
  ) %>%
  # Model predictions
  rowwise() %>%
  mutate(
    model_p_correct = softmax(
      option_values = c(opt1_distance, opt2_distance),
      option_chosen = if_else(correct_choice == opt1_id, 1, 2),
      temperature = softmax_temperature,
      use_inverse_temperature = TRUE
    )
  ) %>%
  ungroup() %>%
  # Add subjects' actual choices
  left_join(
    bind_rows(nav_study1, nav_study2, nav_study3) %>%
      select(
        study, sub_id, measurement_id,
        shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
        sub_choice, sub_correct = correct, sub_rt = rt
      ),
    by = join_by(
      study, sub_id, measurement_id,
      shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
    )
  )
ppc_sr_representation <- params %>%
  filter(model == "sr_analytic") %>%
  pivot_wider(names_from = param_name, values_from = param_value) %>%
  select(study, sub_id, measurement_id, sr_gamma) %>%
  rowwise() %>%
  mutate(
    predicted_sr = map(
      .x = sr_gamma,
      .f = ~build_successor_analytically(
        transmat, successor_horizon = .x, normalize = TRUE
      )
    )
  ) %>%
  ungroup() %>%
  select(study, sub_id, measurement_id, predicted_sr) %>%
  unnest(predicted_sr)

ppc_sr_navigation <- expand_grid(
  # List of all trials for each subject/measurement
  params %>%
    select(study, sub_id, measurement_id) %>%
    distinct(),
  nav_trials
) %>%
  # Add subject-specific parameters
  left_join(
    params %>%
      filter(model == "sr_analytic") %>%
      pivot_wider(names_from = param_name, values_from = param_value) %>%
      select(study, sub_id, measurement_id, softmax_temperature),
    by = join_by(study, sub_id, measurement_id)
  ) %>%
  # Add SR predicted representation
  left_join(
    ppc_sr_representation %>%
      select(
        study, sub_id, measurement_id,
        endpoint_id = to, opt1_id = from, opt1_sr = sr_value
      ),
    by = join_by(study, sub_id, measurement_id, endpoint_id, opt1_id)
  ) %>%
  left_join(
    ppc_sr_representation %>%
      select(
        study, sub_id, measurement_id,
        endpoint_id = to, opt2_id = from, opt2_sr = sr_value
      ),
    by = join_by(study, sub_id, measurement_id, endpoint_id, opt2_id)
  ) %>%
  # Model navigation predictions
  rowwise() %>%
  mutate(
    model_p_correct = softmax(
      option_values = c(opt1_sr, opt2_sr),
      option_chosen = if_else(correct_choice == opt1_id, 1, 2),
      temperature = softmax_temperature,
      use_inverse_temperature = TRUE
    )
  ) %>%
  ungroup() %>%
  # Add subjects' actual choices
  left_join(
    bind_rows(nav_study1, nav_study2, nav_study3) %>%
      select(
        study, sub_id, measurement_id,
        shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
        sub_choice, sub_correct = correct, sub_rt = rt
      ),
    by = join_by(
      study, sub_id, measurement_id,
      shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
    )
  )

Plot PPC

ppc_for_plotting <- params %>%
  select(study, sub_id, measurement_id) %>%
  distinct() %>%
  # Add human accuracy + BFS-backward accuracy
  left_join(
    ppc_bfs_backward %>%
      filter(two_correct_options == FALSE) %>%
      group_by(study, sub_id, measurement_id, shortest_path) %>%
      summarise(
        human = mean(sub_correct),
        bfs_backward = mean(model_p_correct),
        .groups = "drop"
      ),
    by = join_by(study, sub_id, measurement_id)
  ) %>%
  # Add BFS-forward accuracy
  left_join(
    ppc_bfs_forward %>%
      filter(two_correct_options == FALSE) %>%
      group_by(study, sub_id, measurement_id, shortest_path) %>%
      summarise(
        bfs_forward = mean(model_p_correct),
        .groups = "drop"
      ),
    by = join_by(study, sub_id, measurement_id, shortest_path)
  ) %>%
  # Add ideal observer accuracy
  left_join(
    ppc_ideal_obs %>%
      filter(two_correct_options == FALSE) %>%
      group_by(study, sub_id, measurement_id, shortest_path) %>%
      summarise(
        ideal_obs = mean(model_p_correct),
        .groups = "drop"
      ),
    by = join_by(study, sub_id, measurement_id, shortest_path)
  ) %>%
  # Add SR accuracy
  left_join(
    ppc_sr_navigation %>%
      filter(two_correct_options == FALSE) %>%
      group_by(study, sub_id, measurement_id, shortest_path) %>%
      summarise(
        sr = mean(model_p_correct),
        .groups = "drop"
      ),
    by = join_by(study, sub_id, measurement_id, shortest_path)
  ) %>%
  # For plotting aesthetics
  pivot_longer(human:sr, names_to = "agent", values_to = "accuracy") %>%
  mutate(
    agent = case_when(
      agent == "human" ~ "Human",
      agent == "bfs_backward" ~ "BFS-backward",
      agent == "bfs_forward" ~ "BFS-forward",
      agent == "ideal_obs" ~ "Ideal observer",
      agent == "sr" ~ "Successor Rep."
    ),
    agent = fct_relevel(agent, "Human", "Successor Rep.")
  )

Let’s look at the main set of trials and compare human performance against the models on Day 1 (i.e., before rest).

plot_ppc_day1 <- ppc_for_plotting %>%
  filter(measurement_id == "D1") %>%
  ggplot(aes(x=shortest_path, y=accuracy)) +
  theme_custom() +
  facet_wrap(~agent, nrow = 1) +
  geom_hline(yintercept = 0.5, linetype = "dashed", color = "blue") +
  geom_line(aes(group = interaction(study, sub_id)), alpha = 0.1) +
  stat_summary(geom = "crossbar", fun = mean, color = "red") +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(name = "Accuracy", labels = scales::percent) +
  ggtitle("Posterior predictive check: Before rest")
  
plot_ppc_day1

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "ppc_day1.pdf"),
    plot = plot_ppc_day1,
    width = 6, height = 3,
    units = "in", dpi = 300
  )
}

We’ll do the same now for Day 2 (i.e., after overnight rest).

plot_ppc_day2 <- ppc_for_plotting %>%
  filter(measurement_id == "D2") %>%
  ggplot(aes(x=shortest_path, y=accuracy)) +
  theme_custom() +
  facet_wrap(~agent, nrow = 1) +
  geom_hline(yintercept = 0.5, linetype = "dashed", color = "blue") +
  geom_line(aes(group = interaction(study, sub_id)), alpha = 0.1) +
  stat_summary(geom = "crossbar", fun = mean, color = "red") +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(name = "Accuracy", labels = scales::percent) +
  ggtitle("Posterior predictive check: After overnight rest")
  
plot_ppc_day2

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "ppc_day2.pdf"),
    plot = plot_ppc_day2,
    width = 6, height = 3,
    units = "in", dpi = 300
  )
}

And finally, for Day 1b (i.e., after awake rest on Day 1), a measurement that was only made in Study 3.

plot_ppc_day1b <- ppc_for_plotting %>%
  filter(measurement_id == "D1b") %>%
  ggplot(aes(x=shortest_path, y=accuracy)) +
  theme_custom() +
  facet_wrap(~agent, nrow = 1) +
  geom_hline(yintercept = 0.5, linetype = "dashed", color = "blue") +
  geom_line(aes(group = interaction(study, sub_id)), alpha = 0.1) +
  stat_summary(geom = "crossbar", fun = mean, color = "red") +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(name = "Accuracy", labels = scales::percent) +
  ggtitle("Posterior predictive check: After awake rest")
  
plot_ppc_day1b

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "ppc_day1b.pdf"),
    plot = plot_ppc_day1b,
    width = 6, height = 3,
    units = "in", dpi = 300
  )
}

Held-out trials

The primary analyses, including the parameter-fitting, were performed on a set of trials where there was always one unambiguously correct answer. For this reason, there was also a subset of trials that got “held out” because the two options had the same shortest path distance from the target.

Several of the computational models (i.e., BFS-backward and ideal observer) therefore predict 50/50% choices on these trials. BFS-forward does not, as it allows for stochasticity in how agents perform searches from each of the two options. Most notably, the SR often predicts that an agent will prefer one option over another, which basically reflects the fact that (e.g.) although Sources A and B have the same shortest path distance to the Target, Source A might have a greater number of short paths to the Target.

Therefore, if subjects’ behaviors are consistent with non-random responding, we’d ideally like to see that the SR is able to make more accurate out-of-sample predictions on these trials.

heldout_likelihoods <- ppc_sr_navigation %>%
  filter(two_correct_options == TRUE) %>%
  select(
    study, sub_id, measurement_id,
    shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
    opt1_sr, opt2_sr, sub_choice, softmax_temperature
  ) %>%
  mutate(
    sr_prefers = case_when(
      opt1_sr == opt2_sr ~ NA_real_,
      opt1_sr > opt2_sr ~ opt1_id,
      TRUE ~ opt2_id
    )
  ) %>%
  # Calculate the likelihood of the subject's choice, given what option
  # the SR would have preferred
  rowwise() %>%
  mutate(
    p_sub_choice = softmax(
      option_values = c(opt1_sr, opt2_sr),
      option_chosen = if_else(sr_prefers == opt1_id, 1, 2),
      temperature = softmax_temperature,
      use_inverse_temperature = TRUE
    ),
    # Fix a few edge cases
    p_sub_choice = case_when(
      is.na(sr_prefers) ~ 0.5,
      is.nan(p_sub_choice) & (sub_choice == sr_prefers) ~ 1,
      # To avoid log(0), use machine epsilon
      is.nan(p_sub_choice) & (sub_choice != sr_prefers) ~ 2.22e-16,
      TRUE ~ p_sub_choice
    )
  ) %>%
  ungroup() %>%
  mutate(neg_ll_sr = neg_loglik_logistic(p_sub_choice)) %>%
  # Tidy up the SR bit
  select(
    study, sub_id, measurement_id,
    shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
    sr_prefers, sub_choice,
    neg_ll_sr
  ) %>%
  # Add likelihoods for BFS-backward, which always predicts 50/50 responding
  mutate(neg_ll_bfs_backward = neg_loglik_logistic(0.5)) %>%
  # Add likelihoods for BFS-forward
  left_join(
    ppc_bfs_forward %>%
      select(
        study, sub_id, measurement_id,
        shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
        p_bfs_chooses_opt1, p_complete_bfs, p_give_up
      ),
    by = join_by(
      study, sub_id, measurement_id,
      shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
    )
  ) %>%
  mutate(
    p_sub_choice = case_when(
      is.na(sr_prefers) ~ 0.5,
      sr_prefers == opt1_id ~ (
        (p_complete_bfs * p_bfs_chooses_opt1) + (p_give_up * 1/2)
      ),
      sr_prefers == opt2_id ~ (
        (p_complete_bfs * (1-p_bfs_chooses_opt1)) + (p_give_up * 1/2)
      )
    ),
    neg_ll_bfs_forward = neg_loglik_logistic(p_sub_choice)
  ) %>%
  # Add likelihoods for ideal observer, which always predicts 50/50 responding
  mutate(neg_ll_ideal_obs = neg_loglik_logistic(0.5)) %>%
  # Tidy up
  select(
    study, sub_id, measurement_id,
    shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id,
    sr_prefers, sub_choice,
    neg_ll_sr, neg_ll_bfs_backward, neg_ll_bfs_forward, neg_ll_ideal_obs
  )

Below, we see that the SR, compared to the other models, is doing a better job of explaining subjects’ choices on held-out trials, though we again note that the likelihoods for the BFS-backward and ideal observer models are for completely random responding. The different studies have different baseline log-likelihoods because they contain a different number of trials (i.e., in Studies 2-3, we’re summing over both Day 1 and Day 2 measurements).

plot_heldout_likelihoods <- heldout_likelihoods %>%
  # Sum so that we get one neg-loglik per subject
  group_by(study, sub_id, measurement_id) %>%
  summarise(across(starts_with("neg_ll_"), sum), .groups = "drop") %>%
  # Prep for plotting
  pivot_longer(
    starts_with("neg_ll_"), names_to = "model", values_to = "neg_ll"
  ) %>%
  mutate(model = str_remove(model, "neg_ll_"), loglik = -neg_ll) %>%
  ggplot(aes(x=measurement_id, y=loglik, color=model)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x") +
  stat_summary(
    geom = "crossbar", fun = median, position = position_dodge(width = 0.75)
  ) +
  geom_point(
    alpha = 0.2,
    position = position_jitterdodge(
      jitter.width = 0.1, jitter.height = 0, dodge.width = 0.75, seed = 1
    ),
    show.legend = FALSE
  ) +
  scale_x_discrete(
    name = NULL,
    labels = c(
      "D1"="Before\nrest",
      "D2"="After\novernight\nrest",
      "D1b"="After\nawake\nrest"
    )
  ) +
  scale_y_continuous(name = "log-likelihood\n(greater = better)") +
  scale_color_manual(
    name = NULL,
    labels = c(
      "bfs_backward" = "BFS-backward",
      "bfs_forward" = "BFS-forward",
      "ideal_obs" = "Ideal observer",
      "sr" = "Successor Rep."
    ),
    values = c(
      "bfs_backward" = "#a6dba0",
      "bfs_forward" = "#5aae61",
      "ideal_obs" = "#1b7837",
      "sr" = "#af8dc3"
    )
  ) +
  theme(legend.position = "bottom") +
  ggtitle("Held-out trials: log-likelihoods")

plot_heldout_likelihoods

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "heldout_likelihoods.pdf"),
    plot = plot_heldout_likelihoods,
    width = 6, height = 4,
    units = "in", dpi = 300
  )
}

Although the likelihoods give us a nice quantitative metric, we may also be interested in knowing how well the SR predicts subjects’ choices just in terms of accuracy. In the below analysis, we’ll use mixed-effects logistic regression to test whether subjects significantly choose the SR-preferred option. Note that we’re removing all of the trials where the SR is indifferent to the two options, as those trials lead us to overestimate the model’s predicted accuracy (i.e., because the subject is always right on those trials). Note also that we’re iteratively re-parameterizing the model with a different reference category each time, so that we can test whether model accuracy is significantly above chance at each distance. Finally, note that for the purpose of statistical testing, we’re looking at the two main measurements: before rest (day 1), and after overnight rest (day 2).

stats_heldout_accuracy_dist2 <- heldout_likelihoods %>%
  filter(measurement_id %in% c("D1", "D2")) %>%
  filter(!is.na(sr_prefers)) %>%
  mutate(
    p_sub_chooses_sr_preference = sub_choice == sr_prefers,
    sub_id = str_c(study, ", s", sub_id)
  ) %>%
  glmmTMB(
    p_sub_chooses_sr_preference ~ shortest_path +
      (1 | sub_id) + (1 | study),
    family = binomial,
    data = .
  )

stats_heldout_accuracy_dist3 <- heldout_likelihoods %>%
  filter(measurement_id %in% c("D1", "D2")) %>%
  filter(!is.na(sr_prefers)) %>%
  mutate(
    p_sub_chooses_sr_preference = sub_choice == sr_prefers,
    sub_id = str_c(study, ", s", sub_id),
    shortest_path = fct_relevel(shortest_path, "3")
  ) %>%
  glmmTMB(
    p_sub_chooses_sr_preference ~ shortest_path +
      (1 | sub_id) + (1 | study),
    family = binomial,
    data = .
  )

stats_heldout_accuracy_dist4 <- heldout_likelihoods %>%
  filter(measurement_id %in% c("D1", "D2")) %>%
  filter(!is.na(sr_prefers)) %>%
  mutate(
    p_sub_chooses_sr_preference = sub_choice == sr_prefers,
    sub_id = str_c(study, ", s", sub_id),
    shortest_path = fct_relevel(shortest_path, "4")
  ) %>%
  glmmTMB(
    p_sub_chooses_sr_preference ~ shortest_path +
      (1 | sub_id) + (1 | study),
    family = binomial,
    data = .
  )

map_dfr(
  .x = list(
    "dist2" = stats_heldout_accuracy_dist2,
    "dist3" = stats_heldout_accuracy_dist3,
    "dist4" = stats_heldout_accuracy_dist4
  ),
  .f = ~tidy(.x) %>% select(-component),
  .id = "ref_cat"
) %>%
  kable_custom(
    "Held-out trials: SR model accuracy",
    grouping_var = ref_cat
  )
Held-out trials: SR model accuracy
effect group term estimate std.error statistic p.value
dist2
fixed NA (Intercept) 0.002 0.058 0.036 0.971
fixed NA shortest_path3 0.307 0.085 3.618 0.000
fixed NA shortest_path4 0.196 0.098 1.993 0.046
ran_pars sub_id sd__(Intercept) 0.289 NA NA NA
ran_pars study sd__(Intercept) 0.000 NA NA NA
dist3
fixed NA (Intercept) 0.309 0.071 4.378 0.000
fixed NA shortest_path2 -0.307 0.085 -3.618 0.000
fixed NA shortest_path4 -0.111 0.106 -1.047 0.295
ran_pars sub_id sd__(Intercept) 0.289 NA NA NA
ran_pars study sd__(Intercept) 0.000 NA NA NA
dist4
fixed NA (Intercept) 0.198 0.086 2.294 0.022
fixed NA shortest_path2 -0.196 0.098 -1.993 0.046
fixed NA shortest_path3 0.111 0.106 1.047 0.295
ran_pars sub_id sd__(Intercept) 0.289 NA NA NA
ran_pars study sd__(Intercept) 0.000 NA NA NA

In the plot below, we can see that there’s quite a bit of variability across subjects (black lines and datapoints), but also that at the group level, the SR achieves above-chance accuracy for the held-out distance-3 and distance-4 trials (red).

predict_heldout_accuracy <- tibble(
  shortest_path = factor(2:4), sub_id = NA, study = NA
) %>%
  predict_glmmTMB(stats_heldout_accuracy_dist2)

plot_heldout_accuracy <- heldout_likelihoods %>%
  filter(measurement_id %in% c("D1", "D2")) %>%
  filter(!is.na(sr_prefers)) %>%
  group_by(study, sub_id, shortest_path) %>%
  summarise(
    p_sub_chooses_sr_preference = mean(sub_choice == sr_prefers),
    .groups = "drop"
  ) %>%
  ggplot(aes(x=shortest_path, y=p_sub_chooses_sr_preference)) +
  theme_custom() +
  geom_hline(yintercept = 0.5, linetype = "dashed") +
  geom_line(aes(group = interaction(study, sub_id)), alpha = 0.1) +
  geom_point(alpha = 0.1) +
  geom_pointrange(
    aes(x = shortest_path, y = fit, ymin = fit - se.fit, ymax = fit + se.fit),
    data = predict_heldout_accuracy, inherit.aes = FALSE,
    # fatten = 1, size = 1,
    linewidth = 1, color = "red"
  ) +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(
    name = "p(Human chooses SR-preferred Source)",
    labels = scales::percent,
    breaks = seq(0, 1, .25),
    expand = expansion(mult = c(0.1, 0.15))
  ) +
  ggtitle("Held-out trials: SR model accuracy")

plot_heldout_accuracy

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "heldout_accuracy.pdf"),
    plot = plot_heldout_accuracy,
    width = 4, height = 5,
    units = "in", dpi = 300
  )
}

McFadden’s pseudo R-squared

Finally, it can be useful to get a sense for each model’s “goodness-of-fit” by computing the ratio of its likelihood to the likelihood of a null model (i.e., McFadden’s R-squared). Here, the null model is an agent that chooses completely at random on every trial.

mcfadden_r2 <- aicc %>%
  filter(model != "sr_delta_rule") %>%
  mutate(loglik = -neg_loglik) %>%
  select(study, sub_id, measurement_id, model, model_loglik = loglik) %>%
  mutate(
    # Null model is chance-level choice on every trial, so this ends up being
    # exactly equal to the usual formulation
    null_loglik = nav_trials %>%
      filter(two_correct_options == FALSE) %>%
      nrow() %>%
      {. * log(0.5)}
  ) %>%
  mutate(mcfadden_r2 = 1 - (model_loglik / null_loglik))

mcfadden_r2 %>%
  group_by(study, measurement_id, model) %>%
  summarise(Mean = mean(mcfadden_r2), .groups = "drop") %>%
  pivot_wider(names_from = model, values_from = Mean) %>%
  kable_custom("Mean McFadden's R2", grouping_var = study)
Mean McFadden’s R2
measurement_id bfs_backward bfs_forward ideal_obs sr_analytic
Study 1
D1 0.185 0.171 0.129 0.200
Study 2
D1 0.252 0.245 0.221 0.275
D2 0.310 0.291 0.293 0.335
Study 3
D1 0.290 0.273 0.266 0.311
D1b 0.281 0.271 0.276 0.306
D2 0.338 0.318 0.335 0.374
mcfadden_r2 %>%
  group_by(study, measurement_id, model) %>%
  summarise(Median = median(mcfadden_r2), .groups = "drop") %>%
  pivot_wider(names_from = model, values_from = Median) %>%
  kable_custom("Median McFadden's R2", grouping_var = study)
Median McFadden’s R2
measurement_id bfs_backward bfs_forward ideal_obs sr_analytic
Study 1
D1 0.114 0.124 0.063 0.151
Study 2
D1 0.184 0.172 0.138 0.195
D2 0.226 0.221 0.188 0.245
Study 3
D1 0.198 0.171 0.134 0.194
D1b 0.166 0.155 0.161 0.196
D2 0.237 0.218 0.209 0.268

We see in the plot that, generally, all models are doing better than the null model, and also that the SR consistently seems to have the best likelihood ratio.

plot_mcfadden_r2 <- mcfadden_r2 %>%
  ggplot(aes(x=measurement_id, y=mcfadden_r2, color=model)) +
  theme_custom() +
  facet_wrap(~study, scales = "free_x") +
  geom_point(
    alpha = 0.1,
    position = position_jitterdodge(
      jitter.width = 0.1, jitter.height = 0, dodge.width = 0.75, seed = 1
    ),
    show.legend = FALSE
  ) +
  stat_summary(
    geom = "crossbar", fun = median, position = position_dodge(width = 0.75)
  ) +
  scale_x_discrete(
    name = NULL,
    labels = c(
      "D1"="Before\nrest",
      "D2"="After\novernight\nrest",
      "D1b"="After\nawake\nrest"
    )
  ) +
  scale_y_continuous(name = bquote(~"McFadden's"~R^2)) +
  scale_color_manual(
    name = NULL,
    labels = c(
      "bfs_backward" = "BFS-backward",
      "bfs_forward" = "BFS-forward",
      "ideal_obs" = "Ideal observer",
      "sr_analytic" = "Successor Representation"
    ),
    values = c(
      "bfs_backward" = "#a6dba0",
      "bfs_forward" = "#5aae61",
      "ideal_obs" = "#1b7837",
      "sr_analytic" = "#af8dc3"
    )
  ) +
  theme(legend.position = "bottom") +
  ggtitle("Model goodness-of-fit")

plot_mcfadden_r2

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "mcfadden_r2.pdf"),
    plot = plot_mcfadden_r2,
    width = 6, height = 4,
    units = "in", dpi = 300
  )
}

Figures for supplement

plot_model_comparison_for_supp <- wrap_plots(
  plot_akaike_group,
  plot_best_fitting_model_prop,
  plot_pxp,
  guides = "collect", nrow = 1
) +
  plot_annotation(
    title = "Model comparison",
    tag_levels = "A", tag_suffix = ".",
    theme = theme(plot.title = element_text(hjust = 0.5))
  ) &
  theme(legend.position = "bottom")

plot_model_comparison_for_supp

if (knitting) {
  ggsave(
    filename = here("figures", "supp_model_comparison.pdf"),
    plot = plot_model_comparison_for_supp,
    width = 16, height = 4,
    units = "in", dpi = 300
  )
}
plot_ppc_for_supp <- wrap_plots(
  plot_ppc_day2 + ggtitle("After overnight rest (Studies 2-3)"),
  plot_ppc_day1b + ggtitle("After awake rest (Study 3)"),
  ncol = 1
) +
  plot_annotation(
    title = "Posterior predictive check",
    tag_levels = "A", tag_suffix = ".",
    theme = theme(plot.title = element_text(hjust = 0.5))
  ) &
  scale_y_continuous(
    name = "Accuracy", labels = scales::percent,
    limits = c(.25, 1),
    breaks = seq(.25, 1, .25)
  )
## Scale for y is already present.
## Adding another scale for y, which will replace the existing scale.
## Scale for y is already present.
## Adding another scale for y, which will replace the existing scale.
plot_ppc_for_supp

if (knitting) {
  ggsave(
    filename = here("figures", "supp_ppc.pdf"),
    plot = plot_ppc_for_supp,
    width = 8, height = 6,
    units = "in", dpi = 300
  )
}

There are some plots that we want to use as-is, so we’ll save a redundant copy in the figures folder.

if (knitting) {
  ggsave(
    filename = here("figures", "supp_param_estimates.pdf"),
    plot = plot_params_all,
    width = 12, height = 6,
    units = "in", dpi = 300
  )
  
  ggsave(
    filename = here("figures", "supp_akaike_weights_per_subject.pdf"),
    plot = plot_akaike_individual,
    width = 8, height = 10,
    units = "in", dpi = 300
  )
  
  ggsave(
    filename = here("figures", "supp_heldout_likelihoods.pdf"),
    plot = plot_heldout_likelihoods,
    width = 6, height = 4,
    units = "in", dpi = 300
  )
  
  ggsave(
    filename = here("figures", "supp_heldout_accuracy.pdf"),
    plot = plot_heldout_accuracy,
    width = 4, height = 5,
    units = "in", dpi = 300
  )
  
  ggsave(
    filename = here("figures", "supp_mcfadden_r2.pdf"),
    plot = plot_mcfadden_r2,
    width = 6, height = 4,
    units = "in", dpi = 300
  )
}
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
LS0tCnRpdGxlOiAiTW9kZWwgY29tcGFyaXNvbiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHRydWUKLS0tCgojIFNldHVwCgpgYGB7ciBsaWJyYXJpZXN9CndvcmtmbG93X25hbWUgPC0gIm5ldG5hdl8wNl9tb2RlbF9jb21wYXJpc29uIgoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoaGVyZSkKbGlicmFyeShwYXRjaHdvcmspCgpsaWJyYXJ5KGdsbW1UTUIpCmxpYnJhcnkoYnJvb20ubWl4ZWQpCgpzb3VyY2UoaGVyZSgiY29kZSIsICJ1dGlscyIsICJtb2RlbGluZ191dGlscy5SIikpCnNvdXJjZShoZXJlKCJjb2RlIiwgInV0aWxzIiwgInJlcHJlc2VudGF0aW9uX3V0aWxzLlIiKSkKc291cmNlKGhlcmUoImNvZGUiLCAidXRpbHMiLCAiYmF5ZXNpYW5fbW9kZWxfc2VsZWN0aW9uLlIiKSkKCnNvdXJjZShoZXJlKCJjb2RlIiwgInV0aWxzIiwgImdncGxvdF90aGVtZXMuUiIpKQpzb3VyY2UoaGVyZSgiY29kZSIsICJ1dGlscyIsICJrYWJsZV91dGlscy5SIikpCnNvdXJjZShoZXJlKCJjb2RlIiwgInV0aWxzIiwgInVuaWNvZGVfZ3JlZWsuUiIpKQoKa25pdHRpbmcgPC0ga25pdHI6OmlzX2h0bWxfb3V0cHV0KCkKCmNyZWF0ZV9wYXRoIDwtIGZ1bmN0aW9uKHRoaXNfcGF0aCkgewogIGlmICghZGlyLmV4aXN0cyh0aGlzX3BhdGgpKSB7CiAgICBkaXIuY3JlYXRlKHRoaXNfcGF0aCwgcmVjdXJzaXZlID0gVFJVRSkKICB9Cn0KCnByZWRpY3RfZ2xtbVRNQiA8LSBmdW5jdGlvbihtYWtlX3ByZWRpY3Rpb25zX2ZvciwgbW9kZWxfb2JqZWN0KSB7CiAgbWFrZV9wcmVkaWN0aW9uc19mb3IgJT4lCiAgICBiaW5kX2NvbHMoCiAgICAgIHByZWRpY3QoCiAgICAgICAgb2JqZWN0ID0gbW9kZWxfb2JqZWN0LAogICAgICAgIG5ld2RhdGEgPSAuLAogICAgICAgIHJlLmZvcm0gPSBOQSwgYWxsb3cubmV3LmxldmVscyA9IFRSVUUsIHNlLmZpdCA9IFRSVUUsIHR5cGUgPSAicmVzcG9uc2UiCiAgICAgICkKICAgICkKfQoKaWYgKGtuaXR0aW5nKSB7CiAgaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUpICU+JQogICAgY3JlYXRlX3BhdGgoKQogIAogIGhlcmUoImZpZ3VyZXMiKSAlPiUKICAgIGNyZWF0ZV9wYXRoKCkKfQpgYGAKCmBgYHtyIGxvYWQtYmVoYXYtZGF0YX0KbmF2X3N0dWR5MSA8LSBoZXJlKCJkYXRhIiwgImNsZWFuX2RhdGEiLCAic3R1ZHkxX21lc3NhZ2VfcGFzc2luZy5jc3YiKSAlPiUKICByZWFkX2NzdihzaG93X2NvbF90eXBlcyA9IEZBTFNFKSAlPiUKICBmaWx0ZXIoCiAgICAjIHR3b19jb3JyZWN0X29wdGlvbnMgPT0gRkFMU0UsCiAgICBzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMgPT0gc2hvcnRlc3RfcGF0aF9naXZlbl9zdGFydF9lbmQKICApICU+JQogIG11dGF0ZSgKICAgIHN0dWR5ID0gIlN0dWR5IDEiLAogICAgbWVhc3VyZW1lbnRfaWQgPSBzdHJfYygiRCIsIG1lYXN1cmVtZW50X2lkKSwKICAgIHNob3J0ZXN0X3BhdGggPSBmYWN0b3Ioc2hvcnRlc3RfcGF0aF9naXZlbl9vcHRzKQogICkgJT4lCiAgc2VsZWN0KAogICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsIHNob3J0ZXN0X3BhdGgsCiAgICBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwKICAgIG9wdDFfaWQsIG9wdDJfaWQsCiAgICBjb3JyZWN0X2Nob2ljZSwgc3ViX2Nob2ljZSwKICAgIGNvcnJlY3QsIHJ0LAogICAgdHdvX2NvcnJlY3Rfb3B0aW9ucywKICAgIG9wdDFfZGlzdGFuY2UgPSBkaXN0X29wdDEsCiAgICBvcHQyX2Rpc3RhbmNlID0gZGlzdF9vcHQyCiAgKQoKbmF2X3N0dWR5MiA8LSBoZXJlKCJkYXRhIiwgImNsZWFuX2RhdGEiLCAic3R1ZHkyX21lc3NhZ2VfcGFzc2luZy5jc3YiKSAlPiUKICByZWFkX2NzdihzaG93X2NvbF90eXBlcyA9IEZBTFNFKSAlPiUKICBmaWx0ZXIoCiAgICAjIHR3b19jb3JyZWN0X29wdGlvbnMgPT0gRkFMU0UsCiAgICBzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMgPT0gc2hvcnRlc3RfcGF0aF9naXZlbl9zdGFydF9lbmQKICApICU+JQogIG11dGF0ZSgKICAgIHN0dWR5ID0gIlN0dWR5IDIiLAogICAgbWVhc3VyZW1lbnRfaWQgPSBjYXNlX3doZW4oCiAgICAgIG5ldHdvcmsgPT0gImxlYXJuZWQiIH4gc3RyX2MoIkQiLCBtZWFzdXJlbWVudF9pZCksCiAgICAgIG5ldHdvcmsgPT0gInJlZXZhbHVhdGVkIiB+ICJEMmIiCiAgICApLAogICAgc2hvcnRlc3RfcGF0aCA9IGZhY3RvcihzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMpCiAgKSAlPiUKICBzZWxlY3QoCiAgICBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2hvcnRlc3RfcGF0aCwKICAgIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLAogICAgb3B0MV9pZCwgb3B0Ml9pZCwKICAgIGNvcnJlY3RfY2hvaWNlLCBzdWJfY2hvaWNlLAogICAgY29ycmVjdCwgcnQsCiAgICB0d29fY29ycmVjdF9vcHRpb25zLAogICAgb3B0MV9kaXN0YW5jZSA9IGRpc3Rfb3B0MSwKICAgIG9wdDJfZGlzdGFuY2UgPSBkaXN0X29wdDIKICApCgpuYXZfc3R1ZHkzIDwtIGhlcmUoImRhdGEiLCAiY2xlYW5fZGF0YSIsICJzdHVkeTNfbWVzc2FnZV9wYXNzaW5nLmNzdiIpICU+JQogIHJlYWRfY3N2KHNob3dfY29sX3R5cGVzID0gRkFMU0UpICU+JQogIGZpbHRlcigKICAgICMgdHdvX2NvcnJlY3Rfb3B0aW9ucyA9PSBGQUxTRSwKICAgIHNob3J0ZXN0X3BhdGhfZ2l2ZW5fb3B0cyA9PSBzaG9ydGVzdF9wYXRoX2dpdmVuX3N0YXJ0X2VuZAogICkgJT4lCiAgbXV0YXRlKAogICAgc3R1ZHkgPSAiU3R1ZHkgMyIsCiAgICBtZWFzdXJlbWVudF9pZCA9IGNhc2Vfd2hlbigKICAgICAgbmV0d29yayA9PSAicmVldmFsdWF0ZWQiIH4gIkQyYiIsCiAgICAgIG1lYXN1cmVtZW50X2lkID09IDEgfiAiRDEiLAogICAgICBtZWFzdXJlbWVudF9pZCA9PSAyIH4gIkQxYiIsCiAgICAgIG1lYXN1cmVtZW50X2lkID09IDMgfiAiRDIiCiAgICApLAogICAgc2hvcnRlc3RfcGF0aCA9IGZhY3RvcihzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMpCiAgKSAlPiUKICBzZWxlY3QoCiAgICBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2hvcnRlc3RfcGF0aCwKICAgIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLAogICAgb3B0MV9pZCwgb3B0Ml9pZCwKICAgIGNvcnJlY3RfY2hvaWNlLCBzdWJfY2hvaWNlLAogICAgY29ycmVjdCwgcnQsCiAgICB0d29fY29ycmVjdF9vcHRpb25zLAogICAgb3B0MV9kaXN0YW5jZSA9IGRpc3Rfb3B0MSwKICAgIG9wdDJfZGlzdGFuY2UgPSBkaXN0X29wdDIKICApCmBgYAoKYGBge3IgbG9hZC1kYXRhLWZvci1wcGN9CmJmc19iYWNrd2FyZF9zaW1zIDwtIGhlcmUoCiAgImRhdGEiLCAiYmZzX3NpbXMiLCAiYmZzX3NpbXNfbGVhcm5lZF9iYWNrd2FyZC5jc3YiCikgJT4lCiAgcmVhZF9jc3Yoc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkgJT4lCiAgZmlsdGVyKAogICAgc2hvcnRlc3RfcGF0aF9naXZlbl9vcHRzID09IHNob3J0ZXN0X3BhdGhfZ2l2ZW5fc3RhcnRfZW5kLAogICAgIyB0d29fY29ycmVjdF9vcHRpb25zID09IEZBTFNFCiAgKSAlPiUKICBtdXRhdGUoc2hvcnRlc3RfcGF0aCA9IGZhY3RvcihzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMpKSAlPiUKICBzZWxlY3QoLXN0YXJ0c193aXRoKCJzaG9ydGVzdF9wYXRoX2dpdmVuIikpICU+JQogIGdyb3VwX2J5KAogICAgc2hvcnRlc3RfcGF0aCwgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQsCiAgICBjb3JyZWN0X2Nob2ljZSwgdHdvX2NvcnJlY3Rfb3B0aW9ucwogICkgJT4lCiAgc3VtbWFyaXNlKAogICAgcF9iZnNfY29ycmVjdCA9IG1lYW4oYmZzX2Nob2ljZSA9PSBjb3JyZWN0X2Nob2ljZSksCiAgICBwX2Jmc19jaG9vc2VzX29wdDEgPSBtZWFuKGJmc19jaG9pY2UgPT0gb3B0MV9pZCksCiAgICBiZnNfdmlzaXRzID0gbWVhbihiZnNfbl92aXNpdHNfdG90YWwpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkKCmJmc19mb3J3YXJkX3NpbXMgPC0gaGVyZSgiZGF0YSIsICJiZnNfc2ltcyIsICJiZnNfc2ltc19sZWFybmVkX2ZvcndhcmQuY3N2IikgJT4lCiAgcmVhZF9jc3Yoc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkgJT4lCiAgZmlsdGVyKAogICAgc2hvcnRlc3RfcGF0aF9naXZlbl9vcHRzID09IHNob3J0ZXN0X3BhdGhfZ2l2ZW5fc3RhcnRfZW5kLAogICAgIyB0d29fY29ycmVjdF9vcHRpb25zID09IEZBTFNFCiAgKSAlPiUKICBtdXRhdGUoc2hvcnRlc3RfcGF0aCA9IGZhY3RvcihzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMpKSAlPiUKICBzZWxlY3QoLXN0YXJ0c193aXRoKCJzaG9ydGVzdF9wYXRoX2dpdmVuIikpICU+JQogIGdyb3VwX2J5KAogICAgc2hvcnRlc3RfcGF0aCwgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQsCiAgICBjb3JyZWN0X2Nob2ljZSwgdHdvX2NvcnJlY3Rfb3B0aW9ucwogICkgJT4lCiAgc3VtbWFyaXNlKAogICAgcF9iZnNfY29ycmVjdCA9IG1lYW4oYmZzX2Nob2ljZSA9PSBjb3JyZWN0X2Nob2ljZSksCiAgICBwX2Jmc19jaG9vc2VzX29wdDEgPSBtZWFuKGJmc19jaG9pY2UgPT0gb3B0MV9pZCksCiAgICBiZnNfdmlzaXRzID0gbWVhbihiZnNfbl92aXNpdHNfdG90YWwpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkKCm5hdl90cmlhbHMgPC0gaGVyZSgiZGF0YSIsICJjbGVhbl9kYXRhIiwgInN0dWR5MV9tZXNzYWdlX3Bhc3NpbmcuY3N2IikgJT4lCiAgcmVhZF9jc3Yoc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkgJT4lCiAgZmlsdGVyKAogICAgIyB0d29fY29ycmVjdF9vcHRpb25zID09IEZBTFNFLAogICAgc2hvcnRlc3RfcGF0aF9naXZlbl9vcHRzID09IHNob3J0ZXN0X3BhdGhfZ2l2ZW5fc3RhcnRfZW5kCiAgKSAlPiUKICBtdXRhdGUoc2hvcnRlc3RfcGF0aCA9IGZhY3RvcihzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMpKSAlPiUKICBmaWx0ZXIoc3ViX2lkID09IDEpICU+JQogIHNlbGVjdCgKICAgIHNob3J0ZXN0X3BhdGgsCiAgICBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwKICAgIG9wdDFfaWQsIG9wdDJfaWQsCiAgICBjb3JyZWN0X2Nob2ljZSwKICAgIG9wdDFfZGlzdGFuY2UgPSBkaXN0X29wdDEsCiAgICBvcHQyX2Rpc3RhbmNlID0gZGlzdF9vcHQyLAogICAgdHdvX2NvcnJlY3Rfb3B0aW9ucwogICkgJT4lCiAgYXJyYW5nZShzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZCkgJT4lCiAgIyBSZXBsYWNlIHVuZGVmaW5lZCBkaXN0YW5jZXMgKGNvcnJlc3BvbmRpbmcgdG8gaW1wb3NzaWJsZSBvcHRpb25zKQogICMgc28gdGhhdCB0aGUgc29mdG1heCBnZXRzIG5vbi1OQSBpbnB1dHM7IHdlIGFzc3VtZSB0aGF0IGltcG9zc2libGUKICAjIG9wdGlvbnMgYXJlIGp1c3QgYXMgYmFkIGFzIHRoZSBsb25nZXN0IGRpc3RhbmNlIGZvdW5kIGluIHRoaXMgc2V0CiAgIyBvZiB0cmlhbHMsIGkuZS4sIGEgZGlzdGFuY2Ugb2YgOAogIG11dGF0ZShhY3Jvc3MoYyhvcHQxX2Rpc3RhbmNlLCBvcHQyX2Rpc3RhbmNlKSwgfnJlcGxhY2VfbmEoLngsIDgpKSkKCmFkamxpc3QgPC0gaGVyZSgiZGF0YSIsICJjbGVhbl9kYXRhIiwgImFkamxpc3RfbGVhcm5lZC5jc3YiKSAlPiUKICByZWFkX2NzdihzaG93X2NvbF90eXBlcyA9IEZBTFNFKQoKdHJhbnNtYXQgPC0gYWRqbGlzdCAlPiUKICBncm91cF9ieShmcm9tKSAlPiUKICBtdXRhdGUoZWRnZSA9IGVkZ2UgLyBzdW0oZWRnZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdG8sIHZhbHVlc19mcm9tID0gZWRnZSkgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJmcm9tIikgJT4lCiAgYXMubWF0cml4KCkKYGBgCgpgYGB7ciBsb2FkLXBhcmFtc30KbG9hZF9wYXJhbXNfZnJvbV9zY3JhdGNoIDwtIEZBTFNFCgppZiAobG9hZF9wYXJhbXNfZnJvbV9zY3JhdGNoID09IFRSVUUpIHsKICBwYXJhbXMgPC0gaGVyZSgiZGF0YSIsICJwYXJhbV9maXRzIikgJT4lCiAgICBmczo6ZGlyX2xzKAogICAgICByZWN1cnNlID0gMSwKICAgICAgcmVnZXhwID0gc3RyX2MoCiAgICAgICAgIl8iLAogICAgICAgICIoYmZzXyhiYWNrd2FyZHxmb3J3YXJkKXwiLAogICAgICAgICJpZGVhbF9vYnN8IiwKICAgICAgICAic3JfKGFuYWx5dGljfGRlbHRhX3J1bGUpKV8iLAogICAgICAgICIoLikrXFwuY3N2IgogICAgICApCiAgICApICU+JQogICAgbWFwX2RmcigKICAgICAgLmYgPSB+cmVhZF9jc3YoLngsIHNob3dfY29sX3R5cGVzID0gRkFMU0UpLAogICAgICAuaWQgPSAiZmlsZW5hbWUiCiAgICApICU+JQogICAgbXV0YXRlKAogICAgICAjIFJlY292ZXIgbW9kZWwgSUQKICAgICAgbW9kZWwgPSBzdHJfZXh0cmFjdCgKICAgICAgICBmaWxlbmFtZSwKICAgICAgICBzdHJfYygKICAgICAgICAgICJfIiwKICAgICAgICAgICIoYmZzXyhiYWNrd2FyZHxmb3J3YXJkKXwiLAogICAgICAgICAgImlkZWFsX29ic3wiLAogICAgICAgICAgInNyXyhhbmFseXRpY3xkZWx0YV9ydWxlKSlfIgogICAgICAgICkKICAgICAgKSwKICAgICAgbW9kZWwgPSBzdHJfc3ViKG1vZGVsLCAyLCAtMiksCiAgICAgICMgUmVjb3ZlciBzdHVkeSBJRAogICAgICBzdHVkeSA9IHN0cl9leHRyYWN0KGZpbGVuYW1lLCAic3R1ZHlbWzpkaWdpdDpdXSIpLAogICAgICBzdHVkeSA9IHN0cl9yZXBsYWNlKHN0dWR5LCAic3R1ZHkiLCAiU3R1ZHkgIiksCiAgICAgICMgUmVjb3ZlciBzdWJqZWN0IElECiAgICAgIHN1Yl9pZCA9IHN0cl9leHRyYWN0KGZpbGVuYW1lLCAic3ViX1tbOmRpZ2l0Ol1dKyIpLAogICAgICBzdWJfaWQgPSBzdHJfcmVtb3ZlKHN1Yl9pZCwgInN1Yl8iKSwKICAgICAgc3ViX2lkID0gYXMuaW50ZWdlcihzdWJfaWQpLAogICAgICAjIFJlY292ZXIgbWVhc3VyZW1lbnQgSUQKICAgICAgbWVhc3VyZW1lbnRfaWQgPSBzdHJfZXh0cmFjdChmaWxlbmFtZSwgIl9EW1s6ZGlnaXQ6XV1iPyIpLAogICAgICBtZWFzdXJlbWVudF9pZCA9IHN0cl9yZW1vdmUobWVhc3VyZW1lbnRfaWQsICJfIiksCiAgICAgICMgR2V0IHBhcmFtZXRlciB2YWx1ZXMKICAgICAgcGFyYW1fdmFsdWUgPSBpZl9lbHNlKAogICAgICAgIGlzLm5hKHBhcmFtX3ZhbHVlX2h1bWFuX3JlYWRhYmxlKSwKICAgICAgICBwYXJhbV92YWx1ZSwKICAgICAgICBwYXJhbV92YWx1ZV9odW1hbl9yZWFkYWJsZQogICAgICApCiAgICApICU+JQogICAgIyBGaW5kIGJlc3QtZml0dGluZyBvcHRpbWl6YXRpb24gcnVuCiAgICBmaWx0ZXIoY29udmVyZ2VuY2UgPT0gImNvbnZlcmdlZCIpICU+JQogICAgZ3JvdXBfYnkobW9kZWwsIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkKSAlPiUKICAgIHNsaWNlX21pbihvcHRpbV92YWx1ZSwgbiA9IDEpICU+JQogICAgdW5ncm91cCgpICU+JQogICAgIyBTb21lIHN1YmplY3RzIG1heSBoYXZlIGhhZCBtdWx0aXBsZSAiYmVzdCIgb3B0aW1pemF0aW9uIHJ1bnMKICAgICMgSW4gdGhhdCBjYXNlLCBqdXN0IGdvIHdpdGggd2hpY2hldmVyICJiZXN0IiBydW4gd2FzIGVzdGltYXRlZCBmaXJzdAogICAgZ3JvdXBfYnkobW9kZWwsIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkKSAlPiUKICAgIHNsaWNlX21pbihvcHRpbWl6ZXJfcnVuLCBuID0gMSkgJT4lCiAgICB1bmdyb3VwKCkgJT4lCiAgICAjIENsZWFuIHVwCiAgICBzZWxlY3QoCiAgICAgIG1vZGVsLCBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwKICAgICAgcGFyYW1fbmFtZSwgcGFyYW1fdmFsdWUsCiAgICAgIG5lZ19sb2dsaWsgPSBvcHRpbV92YWx1ZQogICAgKSAlPiUKICAgIGFycmFuZ2UobW9kZWwsIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBwYXJhbV9uYW1lKQogIAogIGhlcmUoImRhdGEiLCAicGFyYW1fZml0cyIsICJjbGVhbl9wYXJhbXMiKSAlPiUKICAgIGNyZWF0ZV9wYXRoKCkKICAKICBwYXJhbXMgJT4lCiAgICB3cml0ZV9jc3YoCiAgICAgIGZpbGUgPSBoZXJlKCJkYXRhIiwgInBhcmFtX2ZpdHMiLCAiY2xlYW5fcGFyYW1zIiwgImNsZWFuX3BhcmFtX2ZpdHMuY3N2IikKICAgICkKfQoKcGFyYW1zIDwtIGhlcmUoImRhdGEiLCAicGFyYW1fZml0cyIsICJjbGVhbl9wYXJhbXMiLCAiY2xlYW5fcGFyYW1fZml0cy5jc3YiKSAlPiUKICByZWFkX2NzdihzaG93X2NvbF90eXBlcyA9IEZBTFNFKQpgYGAKCgojIEFJQ2MKCkFzIG91ciBtZXRyaWMgb2YgbG9nLWV2aWRlbmNlLCB3ZSdsbCB1c2UgQUlDYywgaS5lLiwgQUlDIGNvcnJlY3RlZCBmb3IgYSByZWxhdGl2ZWx5IHNtYWxsIE4uCgpgYGB7ciBjYWxjLWFpY2N9CmFpY2MgPC0gcGFyYW1zICU+JQogIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgbW9kZWwsIG5lZ19sb2dsaWspICU+JQogIGRpc3RpbmN0KCkgJT4lCiAgYXJyYW5nZShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgbXV0YXRlKAogICAgbl9wYXJhbXMgPSBpZl9lbHNlKG1vZGVsID09ICJzciIsIDIsIDEpLAogICAgbl9kYXRhcG9pbnRzID0gMTE1LAogICAgYWljID0gKC0yICogLW5lZ19sb2dsaWspICsgKDIgKiBuX3BhcmFtcyksCiAgICBhaWNjID0gYWljICsgKAogICAgICAoMiAqIG5fcGFyYW1zICogKG5fcGFyYW1zICsgMSkpIC8gKG5fZGF0YXBvaW50cyAtIG5fcGFyYW1zIC0gMSkKICAgICkKICApCmBgYAoKCiMgU1IgYW5hbHl0aWMgdnMgZGVsdGEtcnVsZQoKQmVmb3JlIHdlIGdvIGFueSBmdXJ0aGVyLCBsZXQncyBqdXN0IGdldCBvbmUgdGFyZ2V0ZWQgY29tcGFyaXNvbiBvdXQgb2YgdGhlIHdheS4gSW4gZWFybGllciBzY3JpcHRzLCB3ZSBzYXcgdGhhdCBTUiBtYXRyaWNlcyBjYW4gYmUgY29uc3RydWN0ZWQgdXNpbmcgYSBjbG9zZWQtZm9ybSBhbmFseXRpYyBzb2x1dGlvbiwgb3IgYSBkZWx0YS1ydWxlIHVwZGF0aW5nIG1lY2hhbmlzbS4gVGhlc2UgZGlmZmVyZW50IGltcGxlbWVudGF0aW9ucyBjYW4sIGluIHByaW5jaXBsZSwgZW5kIHVwIG1ha2luZyB2ZXJ5IGRpZmZlcmVudCBwcmVkaWN0aW9ucy4gSGVyZSwgd2UnbGwgc2VlIHdoZXRoZXIgdGhlcmUncyBldmlkZW5jZSB0aGF0IG9uZSBpbXBsZW1lbnRhdGlvbiBmaXRzIGJldHRlciB0aGFuIHRoZSBvdGhlci4KCkJlbG93LCB3ZSdyZSBkaXJlY3RseSBjb21wYXJpbmcgdGhlIEFJQ2Mgb2YgdGhlIGFuYWx5dGljIHZzIGRlbHRhLXJ1bGUgaW1wbGVtZW50YXRpb25zLiBFYWNoIGRhdGFwb2ludCBpcyBvbmUgc3ViamVjdCwgYW5kIHRoZSBsaW5lcyBjb25uZWN0IGEgc3ViamVjdCdzIEFJQ2MgZnJvbSBvbmUgaW1wbGVtZW50YXRpb24gdG8gdGhlIG90aGVyLiBUaGUgcmVkIGJhcnMgcmVmbGVjdCB0aGUgbWVhbnMuIFRoZSBsaW5lcyBhcmUgcmVtYXJrYWJseSBmbGF0LCBpbmRpY2F0aW5nIHRoYXQgdGhlcmUgaXMgZnVuY3Rpb25hbGx5IG5vIHJlYWwgZGlmZmVyZW5jZSBpbiB0aGUgbW9kZWwgZ29vZG5lc3Mtb2YtZml0LgoKYGBge3Igc3ItY29tcGFyaXNvbi1haWNjfQpwbG90X3NyX2NvbXBhcmlzb25fYWljYyA8LSBhaWNjICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KG1vZGVsLCAic3JfIikpICU+JQogIG11dGF0ZShmYWNldF9sYWJlbCA9IHN0cl9jKHN0dWR5LCAiLCAiLCBtZWFzdXJlbWVudF9pZCkpICU+JQogIGdncGxvdChhZXMoeD1tb2RlbCwgeT1haWNjKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5mYWNldF9sYWJlbCwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IHN1Yl9pZCksIGFscGhhID0gMC4yKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWFuLCBjb2xvciA9ICJyZWQiKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgKICAgIG5hbWUgPSAiU1IgaW1wbGVtZW50YXRpb24iLAogICAgbGFiZWxzID0gYygic3JfYW5hbHl0aWMiPSJBbmFseXRpYyIsICJzcl9kZWx0YV9ydWxlIj0iRGVsdGEtcnVsZSIpCiAgKSArCiAgeWxhYigiQUlDYyIpICsKICBnZ3RpdGxlKCJBSUNjIGNvbXBhcmlzb24gb2YgU1IgaW1wbGVtZW50YXRpb25zIikKCnBsb3Rfc3JfY29tcGFyaXNvbl9haWNjCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoCiAgICAgICJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwKICAgICAgInNyX2FpY2NfYW5hbHl0aWNfdnNfZGVsdGFfcnVsZS5wZGYiCiAgICApLAogICAgcGxvdCA9IHBsb3Rfc3JfY29tcGFyaXNvbl9haWNjLAogICAgd2lkdGggPSA2LCBoZWlnaHQgPSA0LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgpXZSdyZSBub3QgaW50ZXJlc3RlZCBpbiBkb2luZyBhbnkgc29ydCBvZiBoeXBvdGhlc2lzIHRlc3RpbmcgaGVyZSwgYnV0IGZvciB0aGUgcHVycG9zZSBvZiBkb2luZyBtb2RlbCBzZWxlY3Rpb24sIHdlIGRvIHdhbnQgdG8ga25vdyB3aGV0aGVyIGNob29zaW5nIG9uZSBpbXBsZW1lbnRhdGlvbiBvdmVyIHRoZSBvdGhlciBtaWdodCByZXN1bHQgaW4gZm9ybWluZyBkaWZmZXJlbnQgY29uY2x1c2lvbnMgYWJvdXQgdGhlIHBhcmFtZXRlciBmaXRzLgoKQmVsb3csIHRoZSBiYXJzIHJlZmxlY3QgbWVkaWFucy4gV2UgY2FuIHNlZSB0aGF0LCBieS1hbmQtbGFyZ2UsIHRoZSB0d28gaW1wbGVtZW50YXRpb25zIHJlc3VsdCBpbiB2ZXJ5IHNpbWlsYXIgZXN0aW1hdGVzLgoKYGBge3Igc3ItY29tcGFyaXNvbi1nYW1tYX0KcGxvdF9zcl9jb21wYXJpc29uX2dhbW1hIDwtIHBhcmFtcyAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChtb2RlbCwgInNyXyIpKSAlPiUKICBmaWx0ZXIocGFyYW1fbmFtZSA9PSAic3JfZ2FtbWEiKSAlPiUKICBzZWxlY3QobW9kZWwsIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBzcl9nYW1tYSA9IHBhcmFtX3ZhbHVlKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWVhc3VyZW1lbnRfaWQsIHk9c3JfZ2FtbWEsIGNvbG9yPW1vZGVsKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX3BvaW50KAogICAgYWxwaGEgPSAwLjUsIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjI1KSwgc2hvdy5sZWdlbmQgPSBGQUxTRQogICkgKwogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBpbnRlcmFjdGlvbihtb2RlbCwgc3ViX2lkKSksIGFscGhhID0gMC4yKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWRpYW4pICsKICB4bGFiKCJNZWFzdXJlbWVudCIpICsKICB5bGFiKCJTUiBnYW1tYSIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoCiAgICBuYW1lID0gIlNSIGltcGxlbWVudGF0aW9uIiwKICAgIGxhYmVscyA9IGMoInNyX2FuYWx5dGljIj0iQW5hbHl0aWMiLCAic3JfZGVsdGFfcnVsZSI9IkRlbHRhLXJ1bGUiKSwKICAgIHZhbHVlcyA9IGMoInNyX2FuYWx5dGljIj0iI2NhMDAyMCIsICJzcl9kZWx0YV9ydWxlIj0iIzA1NzFiMCIpCiAgKSArCiAgZ2d0aXRsZSgiRXN0aW1hdGVzIG9mIFNSIGdhbW1hOiBBbmFseXRpYyB2cyBkZWx0YS1ydWxlIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQoKcGxvdF9zcl9jb21wYXJpc29uX2dhbW1hCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoCiAgICAgICJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwKICAgICAgInNyX2dhbW1hX2FuYWx5dGljX3ZzX2RlbHRhX3J1bGUucGRmIgogICAgKSwKICAgIHBsb3QgPSBwbG90X3NyX2NvbXBhcmlzb25fZ2FtbWEsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDQsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKfQpgYGAKCkluIHRoZSBtb2RlbGluZywgdGhlIGdvYWwgd2FzIHRvIHRlc3QgY2hhbmdlcyBpbiB0aGUgZ2FtbWEgcGFyYW1ldGVyLCBhc3N1bWluZyBhbiBhc3ltcHRvdGljIHJlcHJlc2VudGF0aW9uLiBUaGVzZSByZXN1bHRzIHN1Z2dlc3QgdGhhdCB0aGVyZSBpcyBub3RoaW5nIGxvc3QgYnkgdXNpbmcgdGhlIGFuYWx5dGljIGNsb3NlZC1mb3JtIGltcGxlbWVudGF0aW9uLCBzbyB3ZSdsbCBzdGljayB3aXRoIHRoYXQgZnJvbSBoZXJlIG9ud2FyZHMuCgoKIyBEZXNjcmlwdGlvbiBvZiBwYXJhbWV0ZXIgZml0cwoKYGBge3IgZGVzY3JpcHRpdmUtcGFyYW0tZml0c30KcGFyYW1zICU+JQogIGdyb3VwX2J5KG1vZGVsLCBzdHVkeSwgbWVhc3VyZW1lbnRfaWQsIHBhcmFtX25hbWUpICU+JQogIHN1bW1hcmlzZSgKICAgIHBhcmFtX21lYW4gPSBtZWFuKHBhcmFtX3ZhbHVlKSwKICAgIHBhcmFtX21lZGlhbiA9IG1lZGlhbihwYXJhbV92YWx1ZSksCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKSAlPiUKICBrYWJsZV9jdXN0b20oCiAgICBjYXB0aW9ucyA9ICJEZXNjcmlwdGl2ZSBzdGF0czogcGFyYW1ldGVyIGZpdHMiLAogICAgZ3JvdXBpbmdfdmFyID0gbW9kZWwKICApCmBgYAoKYGBge3IgcGxvdC1wYXJhbS1maXRzfQpwbG90X3BhcmFtc19iZnNfYmFja3dhcmQgPC0gcGFyYW1zICU+JQogIGZpbHRlcihtb2RlbCA9PSAiYmZzX2JhY2t3YXJkIikgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHBhcmFtX25hbWUsIHZhbHVlc19mcm9tID0gcGFyYW1fdmFsdWUpICU+JQogIGdncGxvdChhZXMoeD1tZWFzdXJlbWVudF9pZCwgeT1zZWFyY2hfdGhyZXNob2xkKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lYW4sIGNvbG9yID0gInJlZCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lZGlhbiwgY29sb3IgPSAiYmx1ZSIpICsKICBnZW9tX3BvaW50KAogICAgYWxwaGEgPSAwLjEsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMSwgaGVpZ2h0ID0gMCwgc2VlZCA9IDEpCiAgKSArCiAgZ2VvbV9saW5lKAogICAgYWVzKGdyb3VwID0gc3ViX2lkKSwgYWxwaGEgPSAwLjI1LAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjEsIGhlaWdodCA9IDAsIHNlZWQgPSAxKQogICkgKwogIHhsYWIoIk1lYXN1cmVtZW50IikgKwogIHlsYWIoIlNlYXJjaCB0aHJlc2hvbGQiKSArCiAgZ2d0aXRsZSgiQkZTLWJhY2t3YXJkIikKCnBsb3RfcGFyYW1zX2Jmc19mb3J3YXJkIDwtIHBhcmFtcyAlPiUKICBmaWx0ZXIobW9kZWwgPT0gImJmc19mb3J3YXJkIikgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHBhcmFtX25hbWUsIHZhbHVlc19mcm9tID0gcGFyYW1fdmFsdWUpICU+JQogIGdncGxvdChhZXMoeD1tZWFzdXJlbWVudF9pZCwgeT1zZWFyY2hfdGhyZXNob2xkKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lYW4sIGNvbG9yID0gInJlZCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lZGlhbiwgY29sb3IgPSAiYmx1ZSIpICsKICBnZW9tX3BvaW50KAogICAgYWxwaGEgPSAwLjEsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMSwgaGVpZ2h0ID0gMCwgc2VlZCA9IDEpCiAgKSArCiAgZ2VvbV9saW5lKAogICAgYWVzKGdyb3VwID0gc3ViX2lkKSwgYWxwaGEgPSAwLjI1LAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjEsIGhlaWdodCA9IDAsIHNlZWQgPSAxKQogICkgKwogIHhsYWIoIk1lYXN1cmVtZW50IikgKwogIHlsYWIoIlNlYXJjaCB0aHJlc2hvbGQiKSArCiAgZ2d0aXRsZSgiQkZTLWZvcndhcmQiKQoKcGxvdF9wYXJhbXNfaWRlYWxfb2JzIDwtIHBhcmFtcyAlPiUKICBmaWx0ZXIobW9kZWwgPT0gImlkZWFsX29icyIpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBwYXJhbV9uYW1lLCB2YWx1ZXNfZnJvbSA9IHBhcmFtX3ZhbHVlKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWVhc3VyZW1lbnRfaWQsIHk9c29mdG1heF90ZW1wZXJhdHVyZSkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfd3JhcCh+c3R1ZHksIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWFuLCBjb2xvciA9ICJyZWQiKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWRpYW4sIGNvbG9yID0gImJsdWUiKSArCiAgZ2VvbV9wb2ludCgKICAgIGFscGhhID0gMC4xLAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjEsIGhlaWdodCA9IDAsIHNlZWQgPSAxKQogICkgKwogIGdlb21fbGluZSgKICAgIGFlcyhncm91cCA9IHN1Yl9pZCksIGFscGhhID0gMC4yNSwKICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xLCBoZWlnaHQgPSAwLCBzZWVkID0gMSkKICApICsKICB4bGFiKCJNZWFzdXJlbWVudCIpICsKICB5bGFiKCJJbnZlcnNlIHRlbXBlcmF0dXJlIikgKwogIGdndGl0bGUoIklkZWFsIG9ic2VydmVyIikKCnBsb3RfcGFyYW1zX3NyX3RlbXAgPC0gcGFyYW1zICU+JQogIGZpbHRlcihtb2RlbCA9PSAic3JfYW5hbHl0aWMiKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gcGFyYW1fbmFtZSwgdmFsdWVzX2Zyb20gPSBwYXJhbV92YWx1ZSkgJT4lCiAgZ2dwbG90KGFlcyh4PW1lYXN1cmVtZW50X2lkLCB5PXNvZnRtYXhfdGVtcGVyYXR1cmUpKSArCiAgdGhlbWVfY3VzdG9tKCkgKwogIGZhY2V0X3dyYXAofnN0dWR5LCBzY2FsZXMgPSAiZnJlZV94IikgKwogIHN0YXRfc3VtbWFyeShnZW9tID0gImNyb3NzYmFyIiwgZnVuID0gbWVhbiwgY29sb3IgPSAicmVkIikgKwogIHN0YXRfc3VtbWFyeShnZW9tID0gImNyb3NzYmFyIiwgZnVuID0gbWVkaWFuLCBjb2xvciA9ICJibHVlIikgKwogIGdlb21fcG9pbnQoCiAgICBhbHBoYSA9IDAuMSwKICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xLCBoZWlnaHQgPSAwLCBzZWVkID0gMSkKICApICsKICBnZW9tX2xpbmUoCiAgICBhZXMoZ3JvdXAgPSBzdWJfaWQpLCBhbHBoYSA9IDAuMjUsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMSwgaGVpZ2h0ID0gMCwgc2VlZCA9IDEpCiAgKSArCiAgeGxhYigiTWVhc3VyZW1lbnQiKSArCiAgeWxhYigiSW52ZXJzZSB0ZW1wZXJhdHVyZSIpICsKICBnZ3RpdGxlKCJTdWNjZXNzb3IgUmVwcmVzZW50YXRpb24iKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDAsIDMwMDApKQoKcGxvdF9wYXJhbXNfc3JfZ2FtbWEgPC0gcGFyYW1zICU+JQogIGZpbHRlcihtb2RlbCA9PSAic3JfYW5hbHl0aWMiKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gcGFyYW1fbmFtZSwgdmFsdWVzX2Zyb20gPSBwYXJhbV92YWx1ZSkgJT4lCiAgZ2dwbG90KGFlcyh4PW1lYXN1cmVtZW50X2lkLCB5PXNyX2dhbW1hKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lYW4sIGNvbG9yID0gInJlZCIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lZGlhbiwgY29sb3IgPSAiYmx1ZSIpICsKICBnZW9tX3BvaW50KAogICAgYWxwaGEgPSAwLjEsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMSwgaGVpZ2h0ID0gMCwgc2VlZCA9IDEpCiAgKSArCiAgZ2VvbV9saW5lKAogICAgYWVzKGdyb3VwID0gc3ViX2lkKSwgYWxwaGEgPSAwLjI1LAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjEsIGhlaWdodCA9IDAsIHNlZWQgPSAxKQogICkgKwogIHhsYWIoIk1lYXN1cmVtZW50IikgKwogIHlsYWIoIkdhbW1hIikgKwogIGdndGl0bGUoIlN1Y2Nlc3NvciBSZXByZXNlbnRhdGlvbiIpCgpwbG90X3BhcmFtc19hbGwgPC0gKAogIChwbG90X3BhcmFtc19iZnNfYmFja3dhcmQgfCBwbG90X3BhcmFtc19iZnNfZm9yd2FyZCB8IHBsb3RfcGFyYW1zX2lkZWFsX29icykgLwogICAgKHBsb3RfcGFyYW1zX3NyX3RlbXAgfCBwbG90X3BhcmFtc19zcl9nYW1tYSkKKSArCiAgcGxvdF9hbm5vdGF0aW9uKAogICAgdGl0bGUgPSAiRXN0aW1hdGVkIHBhcmFtZXRlcnMiLAogICAgdGFnX2xldmVscyA9ICJBIiwgdGFnX3N1ZmZpeCA9ICIuIiwKICAgIHRoZW1lID0gdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgKQoKcGxvdF9wYXJhbXNfYWxsCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAicGFyYW1fZXN0aW1hdGVzLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfcGFyYW1zX2FsbCwKICAgIHdpZHRoID0gMTIsIGhlaWdodCA9IDYsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKfQpgYGAKCgojIEFrYWlrZSB3ZWlnaHRzCgpXZSdsbCBsYXRlciB1c2UgcHJvdGVjdGVkIGV4Y2VlZGFuY2UgcHJvYmFiaWxpdGllcyAoUFhQKSB0byBkbyBmb3JtYWwgaW5mZXJlbmNlIHRvIHRlc3Qgd2hldGhlciBhIHBhcnRpY3VsYXIgbW9kZWwgcHJvdmlkZXMgYSBzaWduaWZpY2FudGx5IGJldHRlciBncm91cC1sZXZlbCBmaXQgdGhhbiBvdGhlciBtb2RlbHMuIEJ1dCBmaXJzdCwgd2UgZG8gd2FudCB0byBhY2tub3dsZWRnZSB0aGF0IHRoZXJlJ3MgbGlrZWx5IHRvIGJlIHNvbWUgaW5kaXZpZHVhbCBkaWZmZXJlbmNlcyBpbiBob3cgd2VsbCBhIHBhcnRpY3VsYXIgbW9kZWwgZml0cyBlYWNoIHN1YmplY3QuIFRvIGdldCBhIHNlbnNlIGZvciB0aGlzLCB3ZSdsbCB1c2UgQWthaWtlIHdlaWdodHMsIHdoaWNoIHByb3ZpZGUgdGhlIHByb2JhYmlsaXR5IHRoYXQgYSBwYXJ0aWN1bGFyIG1vZGVsIGlzIHRoZSAiYmVzdCIgZ2l2ZW4gdGhlIGRhdGEgYW5kIHRoZSBzZXQgb2YgY2FuZGlkYXRlIG1vZGVscy4KCmBgYHtyIGNhbGMtYWthaWtlLXdlaWdodHN9CmFrYWlrZV93ZWlnaHRzIDwtIGFpY2MgJT4lCiAgZmlsdGVyKG1vZGVsICE9ICJzcl9kZWx0YV9ydWxlIikgJT4lCiAgZ3JvdXBfYnkoc3R1ZHksIG1lYXN1cmVtZW50X2lkLCBzdWJfaWQpICU+JQogIG11dGF0ZSgKICAgIHJlbGF0aXZlX2xpa2VsaWhvb2QgPSBleHAoLTEvMiAqIChhaWNjIC0gbWluKGFpY2MpKSksCiAgICBha2Fpa2Vfd2VpZ2h0ID0gcmVsYXRpdmVfbGlrZWxpaG9vZCAvIHN1bShyZWxhdGl2ZV9saWtlbGlob29kKSwKICAgIGV2aWRlbmNlX3JhdGlvID0gbWF4KGFrYWlrZV93ZWlnaHQpIC8gYWthaWtlX3dlaWdodAogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIGFycmFuZ2Uoc3R1ZHksIG1lYXN1cmVtZW50X2lkLCBzdWJfaWQsIGV2aWRlbmNlX3JhdGlvKQpgYGAKCldlIGNhbiBmaXJzdCBhdmVyYWdlIG92ZXIgYWxsIHN1YmplY3RzJyBBa2Fpa2Ugd2VpZ2h0cyB0byBnZXQgYSBzZW5zZSBmb3Igd2hhdCB0aGUgImJlc3QtZml0dGluZyIgbW9kZWwgaXMgYWNyb3NzIHN1YmplY3RzLiBUaGlzIHN1Z2dlc3RzIHRoYXQgdGhlIFNSIGNvbnNpc3RlbnRseSBjb21lcyBvdXQgb24gdG9wLCBmb2xsb3dlZCBwcmV0dHkgY29uc2lzdGVudGx5IGJ5IEJGUy1iYWNrd2FyZC4KCmBgYHtyIHBsb3QtZ3JvdXAtYWthaWtlLXdlaWdodHN9CnBsb3RfYWthaWtlX2dyb3VwIDwtIGFrYWlrZV93ZWlnaHRzICU+JQogIGdyb3VwX2J5KHN0dWR5LCBtZWFzdXJlbWVudF9pZCwgbW9kZWwpICU+JQogIHN1bW1hcmlzZShha2Fpa2Vfd2VpZ2h0ID0gbWVhbihha2Fpa2Vfd2VpZ2h0KSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgbXV0YXRlKHRleHQgPSByb3VuZChha2Fpa2Vfd2VpZ2h0LCAyKSkgJT4lCiAgZ2dwbG90KGFlcyh4PW1lYXN1cmVtZW50X2lkLCB5PWFrYWlrZV93ZWlnaHQsIGZpbGw9bW9kZWwpKSArCiAgdGhlbWVfY3VzdG9tKCkgKwogIGZhY2V0X3dyYXAofnN0dWR5LCBzY2FsZXMgPSAiZnJlZV94IikgKwogIGdlb21fY29sKCkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSB0ZXh0KSwgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSkpICsKICBzY2FsZV94X2Rpc2NyZXRlKAogICAgbmFtZSA9IE5VTEwsCiAgICBsYWJlbHMgPSBjKAogICAgICAiRDEiPSJCZWZvcmVcbnJlc3QiLAogICAgICAiRDIiPSJBZnRlclxub3Zlcm5pZ2h0XG5yZXN0IiwKICAgICAgIkQxYiI9IkFmdGVyXG5hd2FrZVxucmVzdCIKICAgICkKICApICsKICBzY2FsZV95X2NvbnRpbnVvdXMoCiAgICBuYW1lID0gTlVMTCwKICAgIGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLjAxLCAwLjAxKSkKICApICsKICBzY2FsZV9maWxsX21hbnVhbCgKICAgIG5hbWUgPSBOVUxMLAogICAgbGFiZWxzID0gYygKICAgICAgImJmc19iYWNrd2FyZCIgPSAiQkZTLWJhY2t3YXJkIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICJCRlMtZm9yd2FyZCIsCiAgICAgICJpZGVhbF9vYnMiID0gIklkZWFsIG9ic2VydmVyIiwKICAgICAgInNyX2FuYWx5dGljIiA9ICJTdWNjZXNzb3IgUmVwcmVzZW50YXRpb24iCiAgICApLAogICAgdmFsdWVzID0gYygKICAgICAgImJmc19iYWNrd2FyZCIgPSAiI2E2ZGJhMCIsCiAgICAgICJiZnNfZm9yd2FyZCIgPSAiIzVhYWU2MSIsCiAgICAgICJpZGVhbF9vYnMiID0gIiMxYjc4MzciLAogICAgICAic3JfYW5hbHl0aWMiID0gIiNhZjhkYzMiCiAgICApCiAgKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpCiAgKSArCiAgZ2d0aXRsZSgiQWthaWtlIHdlaWdodHMiKQoKcGxvdF9ha2Fpa2VfZ3JvdXAKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJha2Fpa2Vfd2VpZ2h0c19ncm91cC5wZGYiKSwKICAgIHBsb3QgPSBwbG90X2FrYWlrZV9ncm91cCwKICAgIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKV2UgY2FuIGJyZWFrIHRoaXMgb3V0IGFuZCBwbG90IGVhY2ggaW5kaXZpZHVhbCBzdWJqZWN0J3MgQWthaWtlIHdlaWdodHMuCgpgYGB7ciBwbG90LWluZGl2aWR1YWwtYWthaWtlLXdlaWdodHN9CnBsb3RfYWthaWtlX2luZGl2aWR1YWwgPC0gYWthaWtlX3dlaWdodHMgJT4lCiAgbXV0YXRlKAogICAgc3ViX2lkID0gZmFjdG9yKHN1Yl9pZCksCiAgICBtZWFzdXJlbWVudF9pZCA9IGNhc2Vfd2hlbigKICAgICAgbWVhc3VyZW1lbnRfaWQgPT0gIkQxIiB+ICJiZWZvcmUgcmVzdCIsCiAgICAgIG1lYXN1cmVtZW50X2lkID09ICJEMWIiIH4gImFmdGVyIGF3YWtlIHJlc3QiLAogICAgICBtZWFzdXJlbWVudF9pZCA9PSAiRDIiIH4gImFmdGVyIG92ZXJuaWdodCByZXN0IgogICAgKSwKICAgIHN0dWR5ID0gc3RyX2Moc3R1ZHksICIsICIsIG1lYXN1cmVtZW50X2lkKSwKICAgIHN0dWR5ID0gZmN0X3JlbGV2ZWwoCiAgICAgIHN0dWR5LAogICAgICAiU3R1ZHkgMSwgYmVmb3JlIHJlc3QiLAogICAgICAiU3R1ZHkgMiwgYmVmb3JlIHJlc3QiLAogICAgICAiU3R1ZHkgMiwgYWZ0ZXIgb3Zlcm5pZ2h0IHJlc3QiLAogICAgICAiU3R1ZHkgMywgYmVmb3JlIHJlc3QiLAogICAgICAiU3R1ZHkgMywgYWZ0ZXIgYXdha2UgcmVzdCIsCiAgICAgICJTdHVkeSAzLCBhZnRlciBvdmVybmlnaHQgcmVzdCIKICAgICkKICApICU+JQogIGdncGxvdChhZXMoeD1zdWJfaWQsIHk9YWthaWtlX3dlaWdodCwgZmlsbD1tb2RlbCkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfd3JhcCh+c3R1ZHksIHNjYWxlcyA9ICJmcmVlX3giLCBuY29sID0gMSkgKwogIGdlb21fY29sKCkgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJTdWJqZWN0IElEIikgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSBOVUxMLAogICAgZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAuMDEsIDAuMDEpKQogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKAogICAgbmFtZSA9IE5VTEwsCiAgICBsYWJlbHMgPSBjKAogICAgICAiYmZzX2JhY2t3YXJkIiA9ICJCRlMtYmFja3dhcmQiLAogICAgICAiYmZzX2ZvcndhcmQiID0gIkJGUy1mb3J3YXJkIiwKICAgICAgImlkZWFsX29icyIgPSAiSWRlYWwgb2JzZXJ2ZXIiLAogICAgICAic3JfYW5hbHl0aWMiID0gIlN1Y2Nlc3NvciBSZXByZXNlbnRhdGlvbiIKICAgICksCiAgICB2YWx1ZXMgPSBjKAogICAgICAiYmZzX2JhY2t3YXJkIiA9ICIjYTZkYmEwIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICIjNWFhZTYxIiwKICAgICAgImlkZWFsX29icyIgPSAiIzFiNzgzNyIsCiAgICAgICJzcl9hbmFseXRpYyIgPSAiI2FmOGRjMyIKICAgICkKICApICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0ID0gMSksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKQogICkgKwogIGdndGl0bGUoIkFrYWlrZSB3ZWlnaHRzIikKCnBsb3RfYWthaWtlX2luZGl2aWR1YWwKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgKICAgICAgIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLAogICAgICAiYWthaWtlX3dlaWdodHNfcGVyX3N1YmplY3QucGRmIgogICAgKSwKICAgIHBsb3QgPSBwbG90X2FrYWlrZV9pbmRpdmlkdWFsLAogICAgd2lkdGggPSA4LCBoZWlnaHQgPSAxMCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKQWthaWtlIHdlaWdodHMgcHJvdmlkZSBhIG5pY2UgZ29vZG5lc3Mtb2YtZml0IG1ldHJpYyB0aGF0IHJlc3BlY3RzIHRoZSBwcm9iYWJpbGlzdGljIGFzcGVjdCBvZiBtb2RlbCBjb21wYXJpc29uLCBhbmQgY2FuIGRvIHNvIGF0IGJvdGggdGhlIGdyb3VwLSBhbmQgaW5kaXZpZHVhbC1sZXZlbC4gSG93ZXZlciwgbW9kZWwgc2VsZWN0aW9uIHJlcXVpcmVzIHVzIHRvIHVsdGltYXRlbHkgbWFrZSBhIGRpc2NyZXRlIGNob2ljZS4gSWYgd2UgbWFkZSBwZXItc3ViamVjdCBkZWNpc2lvbnMgYmFzZWQgc2ltcGx5IGJ5IGNob29zaW5nIHRoZSBzaW5nbGUgYmVzdC1maXR0aW5nIG1vZGVsLCB3aGF0IHByb3BvcnRpb24gb2Ygc3ViamVjdHMgYXJlIGJlc3QtZml0IGJ5IGVhY2ggbW9kZWw/IFdlIHNlZSB0aGF0IHRoZSBwYXR0ZXJuIG9mIHJlc3VsdHMgYmFzaWNhbGx5IG1pcnJvcnMgd2hhdCB3ZSdkIHNlZW4gaW4gdGhlIEFrYWlrZSB3ZWlnaHRzLCBzdWNoIHRoYXQgdGhlIFNSIGlzIHRoZSBiZXN0LWZpdHRpbmcgbW9kZWwgZm9yIHRoZSBtYWpvcml0eSBvZiBzdWJqZWN0cywgZm9sbG93ZWQgYnkgQkZTLWJhY2t3YXJkLgoKYGBge3IgcGxvdC1ncm91cC1wcm9wLWJlc3QtZml0c30KYmVzdF9maXR0aW5nX21vZGVsX3Blcl9zdWIgPC0gYWthaWtlX3dlaWdodHMgJT4lCiAgZ3JvdXBfYnkoc3R1ZHksIG1lYXN1cmVtZW50X2lkLCBzdWJfaWQpICU+JQogIHNsaWNlX21heChha2Fpa2Vfd2VpZ2h0KSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgc2VsZWN0KHN0dWR5LCBtZWFzdXJlbWVudF9pZCwgc3ViX2lkLCBiZXN0X2ZpdHRpbmdfbW9kZWwgPSBtb2RlbCkKCnBsb3RfYmVzdF9maXR0aW5nX21vZGVsX3Byb3AgPC0gYmVzdF9maXR0aW5nX21vZGVsX3Blcl9zdWIgJT4lCiAgY291bnQoc3R1ZHksIG1lYXN1cmVtZW50X2lkLCBiZXN0X2ZpdHRpbmdfbW9kZWwpICU+JQogIGdyb3VwX2J5KHN0dWR5LCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgbXV0YXRlKAogICAgcCA9IG4gLyBzdW0obiksCiAgICB0ZXh0ID0gc3RyX2Mocm91bmQocCwgMikgKiAxMDAsICIlIikKICApICU+JQogIGdncGxvdChhZXMoeD1tZWFzdXJlbWVudF9pZCwgeT1wLCBmaWxsPWJlc3RfZml0dGluZ19tb2RlbCkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfd3JhcCh+c3R1ZHksIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgZ2VvbV9jb2woKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHRleHQpLCBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSkgKwogIHNjYWxlX3hfZGlzY3JldGUoCiAgICBuYW1lID0gTlVMTCwKICAgIGxhYmVscyA9IGMoCiAgICAgICJEMSI9IkJlZm9yZVxucmVzdCIsCiAgICAgICJEMiI9IkFmdGVyXG5vdmVybmlnaHRcbnJlc3QiLAogICAgICAiRDFiIj0iQWZ0ZXJcbmF3YWtlXG5yZXN0IgogICAgKQogICkgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSBOVUxMLAogICAgZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAuMDEsIDAuMDEpKQogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKAogICAgbmFtZSA9IE5VTEwsCiAgICBsYWJlbHMgPSBjKAogICAgICAiYmZzX2JhY2t3YXJkIiA9ICJCRlMtYmFja3dhcmQiLAogICAgICAiYmZzX2ZvcndhcmQiID0gIkJGUy1mb3J3YXJkIiwKICAgICAgImlkZWFsX29icyIgPSAiSWRlYWwgb2JzZXJ2ZXIiLAogICAgICAic3JfYW5hbHl0aWMiID0gIlN1Y2Nlc3NvciBSZXByZXNlbnRhdGlvbiIKICAgICksCiAgICB2YWx1ZXMgPSBjKAogICAgICAiYmZzX2JhY2t3YXJkIiA9ICIjYTZkYmEwIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICIjNWFhZTYxIiwKICAgICAgImlkZWFsX29icyIgPSAiIzFiNzgzNyIsCiAgICAgICJzcl9hbmFseXRpYyIgPSAiI2FmOGRjMyIKICAgICkKICApICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCkKICApICsKICBnZ3RpdGxlKCJQcm9wb3J0aW9uIG9mIHN1YmplY3RzIGJlc3QgZml0IGJ5IGVhY2ggbW9kZWwiKQoKcGxvdF9iZXN0X2ZpdHRpbmdfbW9kZWxfcHJvcAoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKAogICAgICAib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsCiAgICAgICJwcm9wX3N1YmplY3RzX2Jlc3RfZml0LnBkZiIKICAgICksCiAgICBwbG90ID0gcGxvdF9iZXN0X2ZpdHRpbmdfbW9kZWxfcHJvcCwKICAgIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKCiMgUHJvdGVjdGVkIGV4Y2VlZGFuY2UgcHJvYmFiaWxpdGllcwoKUHJvdGVjdGVkIGV4Y2VlZGFuY2UgcHJvYmFiaWxpdGllcyBwcm92aWRlIGEgZm9ybWFsIHRlc3Qgb2YgYSBtb2RlbOKAmXMgZ3JvdXAtbGV2ZWwgZml0IGNvbXBhcmVkIHRvIG90aGVyIGNhbmRpZGF0ZSBtb2RlbHMuIFRvIHJ1biB0aGlzIGFuYWx5c2lzLCB3ZSdsbCBhZGFwdCBzb2Z0d2FyZSBvcmlnaW5hbGx5IHdyaXR0ZW4gYnkgTWF0dGVvIExpc2kgKGh0dHBzOi8vZ2l0aHViLmNvbS9tYXR0ZWxpc2kvYm1zUikuIFdlJ2xsIHVzZSBBSUNjIGFzIG91ciBtZXRyaWMgb2YgbG9nLWV2aWRlbmNlLgoKYGBge3IgY2FsYy1weHB9CnB4cF9yZXN1bHRzIDwtIGFpY2MgJT4lCiAgZmlsdGVyKG1vZGVsICE9ICJzcl9kZWx0YV9ydWxlIikgJT4lCiAgIyBJbiBQWFAsIG1vcmUgaXMgbW9yZS4gQUlDYywgaW4gY29udHJhc3QsIGlzIGJhc2VkIG9mZiBuZWctTEwsIGFuZCBzbyBpcwogICMgaW50ZXJwcmV0ZWQgYXMgInNtYWxsZXIgaXMgYmV0dGVyIi4gU28sIGRvIGEgc2lnbiBmbGlwLgogIG11dGF0ZShhaWNjID0gLWFpY2MpICU+JQogIHNlbGVjdChzdHVkeSwgbWVhc3VyZW1lbnRfaWQsIHN1Yl9pZCwgbW9kZWwsIGFpY2MpICU+JQogICMgQ29tcHV0ZSBQWFAgZm9yIGVhY2ggc3R1ZHkvbWVhc3VyZW1lbnQKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gbW9kZWwsIHZhbHVlc19mcm9tID0gYWljYykgJT4lCiAgc2VsZWN0KC1zdWJfaWQpICU+JQogIGdyb3VwX2J5KHN0dWR5LCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgbmVzdCgpICU+JQogIG11dGF0ZSgKICAgIHRlc3QgPSBtYXAoCiAgICAgIC54ID0gZGF0YSwKICAgICAgLmYgPSB+YmF5ZXNpYW5fbW9kZWxfc2VsZWN0aW9uKC54KQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIHVubmVzdCh0ZXN0KSAlPiUKICBzZWxlY3QoLWRhdGEpCmBgYAoKSW4gdGhlIHJlc3VsdHMsIGl0J3MgY2xlYXIgdGhhdCB0aGUgU1IgY29tZXMgb3V0IG9uIHRvcCwgYW5kIGJ5IGEgbGFyZ2UgbWFyZ2luLgoKYGBge3IgcHhwLXJlc3VsdHN9CnB4cF9yZXN1bHRzICU+JQogIG11dGF0ZSgKICAgIG1lYXN1cmVtZW50X2lkID0gY2FzZV93aGVuKAogICAgICBtZWFzdXJlbWVudF9pZCA9PSAiRDEiIH4gImJlZm9yZSByZXN0IiwKICAgICAgbWVhc3VyZW1lbnRfaWQgPT0gIkQxYiIgfiAiYWZ0ZXIgYXdha2UgcmVzdCIsCiAgICAgIG1lYXN1cmVtZW50X2lkID09ICJEMiIgfiAiYWZ0ZXIgb3Zlcm5pZ2h0IHJlc3QiCiAgICApLAogICAgc3R1ZHkgPSBzdHJfYyhzdHVkeSwgIiwgIiwgbWVhc3VyZW1lbnRfaWQpLAogICAgc3R1ZHkgPSBmY3RfcmVsZXZlbCgKICAgICAgc3R1ZHksCiAgICAgICJTdHVkeSAxLCBiZWZvcmUgcmVzdCIsCiAgICAgICJTdHVkeSAyLCBiZWZvcmUgcmVzdCIsCiAgICAgICJTdHVkeSAyLCBhZnRlciBvdmVybmlnaHQgcmVzdCIsCiAgICAgICJTdHVkeSAzLCBiZWZvcmUgcmVzdCIsCiAgICAgICJTdHVkeSAzLCBhZnRlciBhd2FrZSByZXN0IiwKICAgICAgIlN0dWR5IDMsIGFmdGVyIG92ZXJuaWdodCByZXN0IgogICAgKQogICkgJT4lCiAgc2VsZWN0KC1tZWFzdXJlbWVudF9pZCkgJT4lCiAgYXJyYW5nZShzdHVkeSwgZGVzYyhweHApKSAlPiUKICBrYWJsZV9jdXN0b20oIlBYUCByZXN1bHRzIiwgZ3JvdXBpbmdfdmFyID0gc3R1ZHkpCmBgYAoKYGBge3IgcGxvdC1weHB9CnBsb3RfcHhwIDwtIHB4cF9yZXN1bHRzICU+JQogIG11dGF0ZSgKICAgIHRleHQgPSByb3VuZChweHAsIDIpICogMTAwLAogICAgdGV4dCA9IGlmX2Vsc2UobW9kZWwgIT0gInNyX2FuYWx5dGljIiwgIiIsIHN0cl9jKHRleHQsICIlIikpCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWVhc3VyZW1lbnRfaWQsIHk9cHhwLCBmaWxsPW1vZGVsKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX2NvbCgpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gdGV4dCksIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgKICAgIG5hbWUgPSBOVUxMLAogICAgbGFiZWxzID0gYygKICAgICAgIkQxIj0iQmVmb3JlXG5yZXN0IiwKICAgICAgIkQyIj0iQWZ0ZXJcbm92ZXJuaWdodFxucmVzdCIsCiAgICAgICJEMWIiPSJBZnRlclxuYXdha2VcbnJlc3QiCiAgICApCiAgKSArCiAgc2NhbGVfeV9jb250aW51b3VzKAogICAgbmFtZSA9IE5VTEwsCiAgICBleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMC4wMSwgMC4wMSkpCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwoCiAgICBuYW1lID0gTlVMTCwKICAgIGxhYmVscyA9IGMoCiAgICAgICJiZnNfYmFja3dhcmQiID0gIkJGUy1iYWNrd2FyZCIsCiAgICAgICJiZnNfZm9yd2FyZCIgPSAiQkZTLWZvcndhcmQiLAogICAgICAiaWRlYWxfb2JzIiA9ICJJZGVhbCBvYnNlcnZlciIsCiAgICAgICJzcl9hbmFseXRpYyIgPSAiU3VjY2Vzc29yIFJlcHJlc2VudGF0aW9uIgogICAgKSwKICAgIHZhbHVlcyA9IGMoCiAgICAgICJiZnNfYmFja3dhcmQiID0gIiNhNmRiYTAiLAogICAgICAiYmZzX2ZvcndhcmQiID0gIiM1YWFlNjEiLAogICAgICAiaWRlYWxfb2JzIiA9ICIjMWI3ODM3IiwKICAgICAgInNyX2FuYWx5dGljIiA9ICIjYWY4ZGMzIgogICAgKQogICkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKQogICkgKwogIGdndGl0bGUoIlByb3RlY3RlZCBleGNlZWRhbmNlIHByb2JhYmlsaXRpZXMiKQoKcGxvdF9weHAKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgKICAgICAgIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLAogICAgICAicHhwLnBkZiIKICAgICksCiAgICBwbG90ID0gcGxvdF9weHAsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDQsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKfQpgYGAKCgojIFBvc3RlcmlvciBwcmVkaWN0aXZlIGNoZWNrCgojIyBTaW11bGF0ZSBwcmVkaWN0ZWQgYmVoYXZpb3JzCgpJdCdzIG5pY2UgdG8gc2VlIGNvbnNpc3RlbmN5IGluIHRoZSAqcXVhbnRpdGF0aXZlKiBtb2RlbCBjb21wYXJpc29uIGFuZCBzZWxlY3Rpb24sIGJ1dCB3ZSdkIGFsc28gbGlrZSB0byBzZWUgaG93IHdlbGwgaHVtYW4gYmVoYXZpb3JzIGFyZSAqcXVhbGl0YXRpdmVseSogZGVzY3JpYmVkIGJ5IG91ciBtb2RlbHMuIFRvIGRvIHRoaXMsIHdlJ2xsIHNpbXVsYXRlIHN1YmplY3RzJyBwcmVkaWN0ZWQgYmVoYXZpb3JzIGdpdmVuIHRoZWlyIG1vZGVsIHBhcmFtZXRlcnMuCgpgYGB7ciBwcGMtYmZzLWJhY2t3YXJkfQpwcGNfYmZzX2JhY2t3YXJkIDwtIGV4cGFuZF9ncmlkKAogICMgTGlzdCBvZiBhbGwgdHJpYWxzIGZyb20gQkZTIHNpbXVsYXRpb24gZm9yIGVhY2ggc3ViamVjdC9tZWFzdXJlbWVudAogIHBhcmFtcyAlPiUKICAgIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgICBkaXN0aW5jdCgpLAogIGJmc19iYWNrd2FyZF9zaW1zCikgJT4lCiAgIyBBZGQgc3ViamVjdC1zcGVjaWZpYyBwYXJhbWV0ZXJzCiAgbGVmdF9qb2luKAogICAgcGFyYW1zICU+JQogICAgICBmaWx0ZXIobW9kZWwgPT0gImJmc19iYWNrd2FyZCIpICU+JQogICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gcGFyYW1fbmFtZSwgdmFsdWVzX2Zyb20gPSBwYXJhbV92YWx1ZSkgJT4lCiAgICAgIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2VhcmNoX3RocmVzaG9sZCksCiAgICBieSA9IGpvaW5fYnkoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQpCiAgKSAlPiUKICAjIFdoYXQncyB0aGUgcHJvYmFiaWxpdHkgb2YgKmNvbXBsZXRpbmcqIEJGUy1vbmxpbmUgYWxsIHRoZSB3YXkgdGhyb3VnaD8KICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9jb21wbGV0ZV9iZnMgPSBzb2Z0bWF4KAogICAgICBvcHRpb25fdmFsdWVzID0gYyhzZWFyY2hfdGhyZXNob2xkLCBiZnNfdmlzaXRzKSwKICAgICAgb3B0aW9uX2Nob3NlbiA9IDEsCiAgICAgIHRlbXBlcmF0dXJlID0gMQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogICMgV2VpZ2ggQkZTIHByZWRpY3Rpb25zIGFjY29yZGluZ2x5CiAgbXV0YXRlKAogICAgcF9naXZlX3VwID0gMSAtIHBfY29tcGxldGVfYmZzLAogICAgbW9kZWxfcF9jb3JyZWN0ID0gKAogICAgICAocF9jb21wbGV0ZV9iZnMgKiBwX2Jmc19jb3JyZWN0KSArIChwX2dpdmVfdXAgKiAxLzIpCiAgICApCiAgKSAlPiUKICAjIEFkZCBzdWJqZWN0cycgYWN0dWFsIGNob2ljZXMKICBsZWZ0X2pvaW4oCiAgICBiaW5kX3Jvd3MobmF2X3N0dWR5MSwgbmF2X3N0dWR5MiwgbmF2X3N0dWR5MykgJT4lCiAgICAgIHNlbGVjdCgKICAgICAgICBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwKICAgICAgICBzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZCwKICAgICAgICBzdWJfY2hvaWNlLCBzdWJfY29ycmVjdCA9IGNvcnJlY3QsIHN1Yl9ydCA9IHJ0CiAgICAgICksCiAgICBieSA9IGpvaW5fYnkoCiAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICBzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZAogICAgKQogICkKYGBgCgpgYGB7ciBwcGMtYmZzLWZvcndhcmR9CnBwY19iZnNfZm9yd2FyZCA8LSBleHBhbmRfZ3JpZCgKICAjIExpc3Qgb2YgYWxsIHRyaWFscyBmcm9tIEJGUyBzaW11bGF0aW9uIGZvciBlYWNoIHN1YmplY3QvbWVhc3VyZW1lbnQKICBwYXJhbXMgJT4lCiAgICBzZWxlY3Qoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQpICU+JQogICAgZGlzdGluY3QoKSwKICBiZnNfZm9yd2FyZF9zaW1zCikgJT4lCiAgIyBBZGQgc3ViamVjdC1zcGVjaWZpYyBwYXJhbWV0ZXJzCiAgbGVmdF9qb2luKAogICAgcGFyYW1zICU+JQogICAgICBmaWx0ZXIobW9kZWwgPT0gImJmc19mb3J3YXJkIikgJT4lCiAgICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBwYXJhbV9uYW1lLCB2YWx1ZXNfZnJvbSA9IHBhcmFtX3ZhbHVlKSAlPiUKICAgICAgc2VsZWN0KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBzZWFyY2hfdGhyZXNob2xkKSwKICAgIGJ5ID0gam9pbl9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkKICApICU+JQogICMgV2hhdCdzIHRoZSBwcm9iYWJpbGl0eSBvZiAqY29tcGxldGluZyogQkZTLW9ubGluZSBhbGwgdGhlIHdheSB0aHJvdWdoPwogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoCiAgICBwX2NvbXBsZXRlX2JmcyA9IHNvZnRtYXgoCiAgICAgIG9wdGlvbl92YWx1ZXMgPSBjKHNlYXJjaF90aHJlc2hvbGQsIGJmc192aXNpdHMpLAogICAgICBvcHRpb25fY2hvc2VuID0gMSwKICAgICAgdGVtcGVyYXR1cmUgPSAxCiAgICApCiAgKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgIyBXZWlnaCBCRlMgcHJlZGljdGlvbnMgYWNjb3JkaW5nbHkKICBtdXRhdGUoCiAgICBwX2dpdmVfdXAgPSAxIC0gcF9jb21wbGV0ZV9iZnMsCiAgICBtb2RlbF9wX2NvcnJlY3QgPSAoCiAgICAgIChwX2NvbXBsZXRlX2JmcyAqIHBfYmZzX2NvcnJlY3QpICsgKHBfZ2l2ZV91cCAqIDEvMikKICAgICkKICApICU+JQogICMgQWRkIHN1YmplY3RzJyBhY3R1YWwgY2hvaWNlcwogIGxlZnRfam9pbigKICAgIGJpbmRfcm93cyhuYXZfc3R1ZHkxLCBuYXZfc3R1ZHkyLCBuYXZfc3R1ZHkzKSAlPiUKICAgICAgc2VsZWN0KAogICAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkLAogICAgICAgIHN1Yl9jaG9pY2UsIHN1Yl9jb3JyZWN0ID0gY29ycmVjdCwgc3ViX3J0ID0gcnQKICAgICAgKSwKICAgIGJ5ID0gam9pbl9ieSgKICAgICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsCiAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkCiAgICApCiAgKQpgYGAKCmBgYHtyIHBwYy1pZGVhbC1vYnN9CnBwY19pZGVhbF9vYnMgPC0gZXhwYW5kX2dyaWQoCiAgIyBMaXN0IG9mIGFsbCB0cmlhbHMgZm9yIGVhY2ggc3ViamVjdC9tZWFzdXJlbWVudAogIHBhcmFtcyAlPiUKICAgIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgICBkaXN0aW5jdCgpLAogIG5hdl90cmlhbHMKKSAlPiUKICAjIEFkZCBzdWJqZWN0LXNwZWNpZmljIHBhcmFtZXRlcnMKICBsZWZ0X2pvaW4oCiAgICBwYXJhbXMgJT4lCiAgICAgIGZpbHRlcihtb2RlbCA9PSAiaWRlYWxfb2JzIikgJT4lCiAgICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBwYXJhbV9uYW1lLCB2YWx1ZXNfZnJvbSA9IHBhcmFtX3ZhbHVlKSAlPiUKICAgICAgc2VsZWN0KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBzb2Z0bWF4X3RlbXBlcmF0dXJlKSwKICAgIGJ5ID0gam9pbl9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkKICApICU+JQogICMgTW9kZWwgcHJlZGljdGlvbnMKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgbW9kZWxfcF9jb3JyZWN0ID0gc29mdG1heCgKICAgICAgb3B0aW9uX3ZhbHVlcyA9IGMob3B0MV9kaXN0YW5jZSwgb3B0Ml9kaXN0YW5jZSksCiAgICAgIG9wdGlvbl9jaG9zZW4gPSBpZl9lbHNlKGNvcnJlY3RfY2hvaWNlID09IG9wdDFfaWQsIDEsIDIpLAogICAgICB0ZW1wZXJhdHVyZSA9IHNvZnRtYXhfdGVtcGVyYXR1cmUsCiAgICAgIHVzZV9pbnZlcnNlX3RlbXBlcmF0dXJlID0gVFJVRQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogICMgQWRkIHN1YmplY3RzJyBhY3R1YWwgY2hvaWNlcwogIGxlZnRfam9pbigKICAgIGJpbmRfcm93cyhuYXZfc3R1ZHkxLCBuYXZfc3R1ZHkyLCBuYXZfc3R1ZHkzKSAlPiUKICAgICAgc2VsZWN0KAogICAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkLAogICAgICAgIHN1Yl9jaG9pY2UsIHN1Yl9jb3JyZWN0ID0gY29ycmVjdCwgc3ViX3J0ID0gcnQKICAgICAgKSwKICAgIGJ5ID0gam9pbl9ieSgKICAgICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsCiAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkCiAgICApCiAgKQpgYGAKCmBgYHtyIHBwYy1zcn0KcHBjX3NyX3JlcHJlc2VudGF0aW9uIDwtIHBhcmFtcyAlPiUKICBmaWx0ZXIobW9kZWwgPT0gInNyX2FuYWx5dGljIikgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHBhcmFtX25hbWUsIHZhbHVlc19mcm9tID0gcGFyYW1fdmFsdWUpICU+JQogIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc3JfZ2FtbWEpICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoCiAgICBwcmVkaWN0ZWRfc3IgPSBtYXAoCiAgICAgIC54ID0gc3JfZ2FtbWEsCiAgICAgIC5mID0gfmJ1aWxkX3N1Y2Nlc3Nvcl9hbmFseXRpY2FsbHkoCiAgICAgICAgdHJhbnNtYXQsIHN1Y2Nlc3Nvcl9ob3Jpem9uID0gLngsIG5vcm1hbGl6ZSA9IFRSVUUKICAgICAgKQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgcHJlZGljdGVkX3NyKSAlPiUKICB1bm5lc3QocHJlZGljdGVkX3NyKQoKcHBjX3NyX25hdmlnYXRpb24gPC0gZXhwYW5kX2dyaWQoCiAgIyBMaXN0IG9mIGFsbCB0cmlhbHMgZm9yIGVhY2ggc3ViamVjdC9tZWFzdXJlbWVudAogIHBhcmFtcyAlPiUKICAgIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCkgJT4lCiAgICBkaXN0aW5jdCgpLAogIG5hdl90cmlhbHMKKSAlPiUKICAjIEFkZCBzdWJqZWN0LXNwZWNpZmljIHBhcmFtZXRlcnMKICBsZWZ0X2pvaW4oCiAgICBwYXJhbXMgJT4lCiAgICAgIGZpbHRlcihtb2RlbCA9PSAic3JfYW5hbHl0aWMiKSAlPiUKICAgICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHBhcmFtX25hbWUsIHZhbHVlc19mcm9tID0gcGFyYW1fdmFsdWUpICU+JQogICAgICBzZWxlY3Qoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsIHNvZnRtYXhfdGVtcGVyYXR1cmUpLAogICAgYnkgPSBqb2luX2J5KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkKQogICkgJT4lCiAgIyBBZGQgU1IgcHJlZGljdGVkIHJlcHJlc2VudGF0aW9uCiAgbGVmdF9qb2luKAogICAgcHBjX3NyX3JlcHJlc2VudGF0aW9uICU+JQogICAgICBzZWxlY3QoCiAgICAgICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsCiAgICAgICAgZW5kcG9pbnRfaWQgPSB0bywgb3B0MV9pZCA9IGZyb20sIG9wdDFfc3IgPSBzcl92YWx1ZQogICAgICApLAogICAgYnkgPSBqb2luX2J5KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCkKICApICU+JQogIGxlZnRfam9pbigKICAgIHBwY19zcl9yZXByZXNlbnRhdGlvbiAlPiUKICAgICAgc2VsZWN0KAogICAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICAgIGVuZHBvaW50X2lkID0gdG8sIG9wdDJfaWQgPSBmcm9tLCBvcHQyX3NyID0gc3JfdmFsdWUKICAgICAgKSwKICAgIGJ5ID0gam9pbl9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgZW5kcG9pbnRfaWQsIG9wdDJfaWQpCiAgKSAlPiUKICAjIE1vZGVsIG5hdmlnYXRpb24gcHJlZGljdGlvbnMKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgbW9kZWxfcF9jb3JyZWN0ID0gc29mdG1heCgKICAgICAgb3B0aW9uX3ZhbHVlcyA9IGMob3B0MV9zciwgb3B0Ml9zciksCiAgICAgIG9wdGlvbl9jaG9zZW4gPSBpZl9lbHNlKGNvcnJlY3RfY2hvaWNlID09IG9wdDFfaWQsIDEsIDIpLAogICAgICB0ZW1wZXJhdHVyZSA9IHNvZnRtYXhfdGVtcGVyYXR1cmUsCiAgICAgIHVzZV9pbnZlcnNlX3RlbXBlcmF0dXJlID0gVFJVRQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogICMgQWRkIHN1YmplY3RzJyBhY3R1YWwgY2hvaWNlcwogIGxlZnRfam9pbigKICAgIGJpbmRfcm93cyhuYXZfc3R1ZHkxLCBuYXZfc3R1ZHkyLCBuYXZfc3R1ZHkzKSAlPiUKICAgICAgc2VsZWN0KAogICAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkLAogICAgICAgIHN1Yl9jaG9pY2UsIHN1Yl9jb3JyZWN0ID0gY29ycmVjdCwgc3ViX3J0ID0gcnQKICAgICAgKSwKICAgIGJ5ID0gam9pbl9ieSgKICAgICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsCiAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkCiAgICApCiAgKQpgYGAKCiMjIFBsb3QgUFBDCgpgYGB7ciBjcmVhdGUtcHBjLWZvci1wbG90dGluZ30KcHBjX2Zvcl9wbG90dGluZyA8LSBwYXJhbXMgJT4lCiAgc2VsZWN0KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkKSAlPiUKICBkaXN0aW5jdCgpICU+JQogICMgQWRkIGh1bWFuIGFjY3VyYWN5ICsgQkZTLWJhY2t3YXJkIGFjY3VyYWN5CiAgbGVmdF9qb2luKAogICAgcHBjX2Jmc19iYWNrd2FyZCAlPiUKICAgICAgZmlsdGVyKHR3b19jb3JyZWN0X29wdGlvbnMgPT0gRkFMU0UpICU+JQogICAgICBncm91cF9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2hvcnRlc3RfcGF0aCkgJT4lCiAgICAgIHN1bW1hcmlzZSgKICAgICAgICBodW1hbiA9IG1lYW4oc3ViX2NvcnJlY3QpLAogICAgICAgIGJmc19iYWNrd2FyZCA9IG1lYW4obW9kZWxfcF9jb3JyZWN0KSwKICAgICAgICAuZ3JvdXBzID0gImRyb3AiCiAgICAgICksCiAgICBieSA9IGpvaW5fYnkoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQpCiAgKSAlPiUKICAjIEFkZCBCRlMtZm9yd2FyZCBhY2N1cmFjeQogIGxlZnRfam9pbigKICAgIHBwY19iZnNfZm9yd2FyZCAlPiUKICAgICAgZmlsdGVyKHR3b19jb3JyZWN0X29wdGlvbnMgPT0gRkFMU0UpICU+JQogICAgICBncm91cF9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2hvcnRlc3RfcGF0aCkgJT4lCiAgICAgIHN1bW1hcmlzZSgKICAgICAgICBiZnNfZm9yd2FyZCA9IG1lYW4obW9kZWxfcF9jb3JyZWN0KSwKICAgICAgICAuZ3JvdXBzID0gImRyb3AiCiAgICAgICksCiAgICBieSA9IGpvaW5fYnkoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsIHNob3J0ZXN0X3BhdGgpCiAgKSAlPiUKICAjIEFkZCBpZGVhbCBvYnNlcnZlciBhY2N1cmFjeQogIGxlZnRfam9pbigKICAgIHBwY19pZGVhbF9vYnMgJT4lCiAgICAgIGZpbHRlcih0d29fY29ycmVjdF9vcHRpb25zID09IEZBTFNFKSAlPiUKICAgICAgZ3JvdXBfYnkoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsIHNob3J0ZXN0X3BhdGgpICU+JQogICAgICBzdW1tYXJpc2UoCiAgICAgICAgaWRlYWxfb2JzID0gbWVhbihtb2RlbF9wX2NvcnJlY3QpLAogICAgICAgIC5ncm91cHMgPSAiZHJvcCIKICAgICAgKSwKICAgIGJ5ID0gam9pbl9ieShzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgc2hvcnRlc3RfcGF0aCkKICApICU+JQogICMgQWRkIFNSIGFjY3VyYWN5CiAgbGVmdF9qb2luKAogICAgcHBjX3NyX25hdmlnYXRpb24gJT4lCiAgICAgIGZpbHRlcih0d29fY29ycmVjdF9vcHRpb25zID09IEZBTFNFKSAlPiUKICAgICAgZ3JvdXBfYnkoc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsIHNob3J0ZXN0X3BhdGgpICU+JQogICAgICBzdW1tYXJpc2UoCiAgICAgICAgc3IgPSBtZWFuKG1vZGVsX3BfY29ycmVjdCksCiAgICAgICAgLmdyb3VwcyA9ICJkcm9wIgogICAgICApLAogICAgYnkgPSBqb2luX2J5KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLCBzaG9ydGVzdF9wYXRoKQogICkgJT4lCiAgIyBGb3IgcGxvdHRpbmcgYWVzdGhldGljcwogIHBpdm90X2xvbmdlcihodW1hbjpzciwgbmFtZXNfdG8gPSAiYWdlbnQiLCB2YWx1ZXNfdG8gPSAiYWNjdXJhY3kiKSAlPiUKICBtdXRhdGUoCiAgICBhZ2VudCA9IGNhc2Vfd2hlbigKICAgICAgYWdlbnQgPT0gImh1bWFuIiB+ICJIdW1hbiIsCiAgICAgIGFnZW50ID09ICJiZnNfYmFja3dhcmQiIH4gIkJGUy1iYWNrd2FyZCIsCiAgICAgIGFnZW50ID09ICJiZnNfZm9yd2FyZCIgfiAiQkZTLWZvcndhcmQiLAogICAgICBhZ2VudCA9PSAiaWRlYWxfb2JzIiB+ICJJZGVhbCBvYnNlcnZlciIsCiAgICAgIGFnZW50ID09ICJzciIgfiAiU3VjY2Vzc29yIFJlcC4iCiAgICApLAogICAgYWdlbnQgPSBmY3RfcmVsZXZlbChhZ2VudCwgIkh1bWFuIiwgIlN1Y2Nlc3NvciBSZXAuIikKICApCmBgYAoKTGV0J3MgbG9vayBhdCB0aGUgbWFpbiBzZXQgb2YgdHJpYWxzIGFuZCBjb21wYXJlIGh1bWFuIHBlcmZvcm1hbmNlIGFnYWluc3QgdGhlIG1vZGVscyBvbiBEYXkgMSAoaS5lLiwgYmVmb3JlIHJlc3QpLgoKYGBge3IgcHBjLWRheTF9CnBsb3RfcHBjX2RheTEgPC0gcHBjX2Zvcl9wbG90dGluZyAlPiUKICBmaWx0ZXIobWVhc3VyZW1lbnRfaWQgPT0gIkQxIikgJT4lCiAgZ2dwbG90KGFlcyh4PXNob3J0ZXN0X3BhdGgsIHk9YWNjdXJhY3kpKSArCiAgdGhlbWVfY3VzdG9tKCkgKwogIGZhY2V0X3dyYXAofmFnZW50LCBucm93ID0gMSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmx1ZSIpICsKICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gaW50ZXJhY3Rpb24oc3R1ZHksIHN1Yl9pZCkpLCBhbHBoYSA9IDAuMSkgKwogIHN0YXRfc3VtbWFyeShnZW9tID0gImNyb3NzYmFyIiwgZnVuID0gbWVhbiwgY29sb3IgPSAicmVkIikgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJTaG9ydGVzdCBwYXRoIGRpc3RhbmNlIikgKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gIkFjY3VyYWN5IiwgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArCiAgZ2d0aXRsZSgiUG9zdGVyaW9yIHByZWRpY3RpdmUgY2hlY2s6IEJlZm9yZSByZXN0IikKICAKcGxvdF9wcGNfZGF5MQoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwgInBwY19kYXkxLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfcHBjX2RheTEsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDMsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKfQpgYGAKCldlJ2xsIGRvIHRoZSBzYW1lIG5vdyBmb3IgRGF5IDIgKGkuZS4sIGFmdGVyIG92ZXJuaWdodCByZXN0KS4KCmBgYHtyIHBwYy1kYXkyfQpwbG90X3BwY19kYXkyIDwtIHBwY19mb3JfcGxvdHRpbmcgJT4lCiAgZmlsdGVyKG1lYXN1cmVtZW50X2lkID09ICJEMiIpICU+JQogIGdncGxvdChhZXMoeD1zaG9ydGVzdF9wYXRoLCB5PWFjY3VyYWN5KSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5hZ2VudCwgbnJvdyA9IDEpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjUsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImJsdWUiKSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IGludGVyYWN0aW9uKHN0dWR5LCBzdWJfaWQpKSwgYWxwaGEgPSAwLjEpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lYW4sIGNvbG9yID0gInJlZCIpICsKICBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiU2hvcnRlc3QgcGF0aCBkaXN0YW5jZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJBY2N1cmFjeSIsIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogIGdndGl0bGUoIlBvc3RlcmlvciBwcmVkaWN0aXZlIGNoZWNrOiBBZnRlciBvdmVybmlnaHQgcmVzdCIpCiAgCnBsb3RfcHBjX2RheTIKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJwcGNfZGF5Mi5wZGYiKSwKICAgIHBsb3QgPSBwbG90X3BwY19kYXkyLAogICAgd2lkdGggPSA2LCBoZWlnaHQgPSAzLAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgpBbmQgZmluYWxseSwgZm9yIERheSAxYiAoaS5lLiwgYWZ0ZXIgYXdha2UgcmVzdCBvbiBEYXkgMSksIGEgbWVhc3VyZW1lbnQgdGhhdCB3YXMgb25seSBtYWRlIGluIFN0dWR5IDMuCgpgYGB7ciBwcGMtZGF5MWJ9CnBsb3RfcHBjX2RheTFiIDwtIHBwY19mb3JfcGxvdHRpbmcgJT4lCiAgZmlsdGVyKG1lYXN1cmVtZW50X2lkID09ICJEMWIiKSAlPiUKICBnZ3Bsb3QoYWVzKHg9c2hvcnRlc3RfcGF0aCwgeT1hY2N1cmFjeSkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfd3JhcCh+YWdlbnQsIG5yb3cgPSAxKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJibHVlIikgKwogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBpbnRlcmFjdGlvbihzdHVkeSwgc3ViX2lkKSksIGFscGhhID0gMC4xKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWFuLCBjb2xvciA9ICJyZWQiKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lID0gIlNob3J0ZXN0IHBhdGggZGlzdGFuY2UiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWUgPSAiQWNjdXJhY3kiLCBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsKICBnZ3RpdGxlKCJQb3N0ZXJpb3IgcHJlZGljdGl2ZSBjaGVjazogQWZ0ZXIgYXdha2UgcmVzdCIpCiAgCnBsb3RfcHBjX2RheTFiCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAicHBjX2RheTFiLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfcHBjX2RheTFiLAogICAgd2lkdGggPSA2LCBoZWlnaHQgPSAzLAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgoKIyBIZWxkLW91dCB0cmlhbHMKClRoZSBwcmltYXJ5IGFuYWx5c2VzLCBpbmNsdWRpbmcgdGhlIHBhcmFtZXRlci1maXR0aW5nLCB3ZXJlIHBlcmZvcm1lZCBvbiBhIHNldCBvZiB0cmlhbHMgd2hlcmUgdGhlcmUgd2FzIGFsd2F5cyBvbmUgdW5hbWJpZ3VvdXNseSBjb3JyZWN0IGFuc3dlci4gRm9yIHRoaXMgcmVhc29uLCB0aGVyZSB3YXMgYWxzbyBhIHN1YnNldCBvZiB0cmlhbHMgdGhhdCBnb3QgImhlbGQgb3V0IiBiZWNhdXNlIHRoZSB0d28gb3B0aW9ucyBoYWQgdGhlIHNhbWUgc2hvcnRlc3QgcGF0aCBkaXN0YW5jZSBmcm9tIHRoZSB0YXJnZXQuCgpTZXZlcmFsIG9mIHRoZSBjb21wdXRhdGlvbmFsIG1vZGVscyAoaS5lLiwgQkZTLWJhY2t3YXJkIGFuZCBpZGVhbCBvYnNlcnZlcikgdGhlcmVmb3JlIHByZWRpY3QgNTAvNTAlIGNob2ljZXMgb24gdGhlc2UgdHJpYWxzLiBCRlMtZm9yd2FyZCBkb2VzIG5vdCwgYXMgaXQgYWxsb3dzIGZvciBzdG9jaGFzdGljaXR5IGluIGhvdyBhZ2VudHMgcGVyZm9ybSBzZWFyY2hlcyBmcm9tIGVhY2ggb2YgdGhlIHR3byBvcHRpb25zLiBNb3N0IG5vdGFibHksIHRoZSBTUiBvZnRlbiBwcmVkaWN0cyB0aGF0IGFuIGFnZW50IHdpbGwgcHJlZmVyIG9uZSBvcHRpb24gb3ZlciBhbm90aGVyLCB3aGljaCBiYXNpY2FsbHkgcmVmbGVjdHMgdGhlIGZhY3QgdGhhdCAoZS5nLikgYWx0aG91Z2ggU291cmNlcyBBIGFuZCBCIGhhdmUgdGhlIHNhbWUgc2hvcnRlc3QgcGF0aCBkaXN0YW5jZSB0byB0aGUgVGFyZ2V0LCBTb3VyY2UgQSBtaWdodCBoYXZlIGEgZ3JlYXRlciBudW1iZXIgb2Ygc2hvcnQgcGF0aHMgdG8gdGhlIFRhcmdldC4KClRoZXJlZm9yZSwgaWYgc3ViamVjdHMnIGJlaGF2aW9ycyBhcmUgY29uc2lzdGVudCB3aXRoIG5vbi1yYW5kb20gcmVzcG9uZGluZywgd2UnZCBpZGVhbGx5IGxpa2UgdG8gc2VlIHRoYXQgdGhlIFNSIGlzIGFibGUgdG8gbWFrZSBtb3JlIGFjY3VyYXRlIG91dC1vZi1zYW1wbGUgcHJlZGljdGlvbnMgb24gdGhlc2UgdHJpYWxzLgoKYGBge3IgY2FsYy1oZWxkb3V0LWxpa2VsaWhvb2RzfQpoZWxkb3V0X2xpa2VsaWhvb2RzIDwtIHBwY19zcl9uYXZpZ2F0aW9uICU+JQogIGZpbHRlcih0d29fY29ycmVjdF9vcHRpb25zID09IFRSVUUpICU+JQogIHNlbGVjdCgKICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgc2hvcnRlc3RfcGF0aCwgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQsCiAgICBvcHQxX3NyLCBvcHQyX3NyLCBzdWJfY2hvaWNlLCBzb2Z0bWF4X3RlbXBlcmF0dXJlCiAgKSAlPiUKICBtdXRhdGUoCiAgICBzcl9wcmVmZXJzID0gY2FzZV93aGVuKAogICAgICBvcHQxX3NyID09IG9wdDJfc3IgfiBOQV9yZWFsXywKICAgICAgb3B0MV9zciA+IG9wdDJfc3IgfiBvcHQxX2lkLAogICAgICBUUlVFIH4gb3B0Ml9pZAogICAgKQogICkgJT4lCiAgIyBDYWxjdWxhdGUgdGhlIGxpa2VsaWhvb2Qgb2YgdGhlIHN1YmplY3QncyBjaG9pY2UsIGdpdmVuIHdoYXQgb3B0aW9uCiAgIyB0aGUgU1Igd291bGQgaGF2ZSBwcmVmZXJyZWQKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9zdWJfY2hvaWNlID0gc29mdG1heCgKICAgICAgb3B0aW9uX3ZhbHVlcyA9IGMob3B0MV9zciwgb3B0Ml9zciksCiAgICAgIG9wdGlvbl9jaG9zZW4gPSBpZl9lbHNlKHNyX3ByZWZlcnMgPT0gb3B0MV9pZCwgMSwgMiksCiAgICAgIHRlbXBlcmF0dXJlID0gc29mdG1heF90ZW1wZXJhdHVyZSwKICAgICAgdXNlX2ludmVyc2VfdGVtcGVyYXR1cmUgPSBUUlVFCiAgICApLAogICAgIyBGaXggYSBmZXcgZWRnZSBjYXNlcwogICAgcF9zdWJfY2hvaWNlID0gY2FzZV93aGVuKAogICAgICBpcy5uYShzcl9wcmVmZXJzKSB+IDAuNSwKICAgICAgaXMubmFuKHBfc3ViX2Nob2ljZSkgJiAoc3ViX2Nob2ljZSA9PSBzcl9wcmVmZXJzKSB+IDEsCiAgICAgICMgVG8gYXZvaWQgbG9nKDApLCB1c2UgbWFjaGluZSBlcHNpbG9uCiAgICAgIGlzLm5hbihwX3N1Yl9jaG9pY2UpICYgKHN1Yl9jaG9pY2UgIT0gc3JfcHJlZmVycykgfiAyLjIyZS0xNiwKICAgICAgVFJVRSB+IHBfc3ViX2Nob2ljZQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShuZWdfbGxfc3IgPSBuZWdfbG9nbGlrX2xvZ2lzdGljKHBfc3ViX2Nob2ljZSkpICU+JQogICMgVGlkeSB1cCB0aGUgU1IgYml0CiAgc2VsZWN0KAogICAgc3R1ZHksIHN1Yl9pZCwgbWVhc3VyZW1lbnRfaWQsCiAgICBzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZCwKICAgIHNyX3ByZWZlcnMsIHN1Yl9jaG9pY2UsCiAgICBuZWdfbGxfc3IKICApICU+JQogICMgQWRkIGxpa2VsaWhvb2RzIGZvciBCRlMtYmFja3dhcmQsIHdoaWNoIGFsd2F5cyBwcmVkaWN0cyA1MC81MCByZXNwb25kaW5nCiAgbXV0YXRlKG5lZ19sbF9iZnNfYmFja3dhcmQgPSBuZWdfbG9nbGlrX2xvZ2lzdGljKDAuNSkpICU+JQogICMgQWRkIGxpa2VsaWhvb2RzIGZvciBCRlMtZm9yd2FyZAogIGxlZnRfam9pbigKICAgIHBwY19iZnNfZm9yd2FyZCAlPiUKICAgICAgc2VsZWN0KAogICAgICAgIHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkLAogICAgICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkLAogICAgICAgIHBfYmZzX2Nob29zZXNfb3B0MSwgcF9jb21wbGV0ZV9iZnMsIHBfZ2l2ZV91cAogICAgICApLAogICAgYnkgPSBqb2luX2J5KAogICAgICBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwKICAgICAgc2hvcnRlc3RfcGF0aCwgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQKICAgICkKICApICU+JQogIG11dGF0ZSgKICAgIHBfc3ViX2Nob2ljZSA9IGNhc2Vfd2hlbigKICAgICAgaXMubmEoc3JfcHJlZmVycykgfiAwLjUsCiAgICAgIHNyX3ByZWZlcnMgPT0gb3B0MV9pZCB+ICgKICAgICAgICAocF9jb21wbGV0ZV9iZnMgKiBwX2Jmc19jaG9vc2VzX29wdDEpICsgKHBfZ2l2ZV91cCAqIDEvMikKICAgICAgKSwKICAgICAgc3JfcHJlZmVycyA9PSBvcHQyX2lkIH4gKAogICAgICAgIChwX2NvbXBsZXRlX2JmcyAqICgxLXBfYmZzX2Nob29zZXNfb3B0MSkpICsgKHBfZ2l2ZV91cCAqIDEvMikKICAgICAgKQogICAgKSwKICAgIG5lZ19sbF9iZnNfZm9yd2FyZCA9IG5lZ19sb2dsaWtfbG9naXN0aWMocF9zdWJfY2hvaWNlKQogICkgJT4lCiAgIyBBZGQgbGlrZWxpaG9vZHMgZm9yIGlkZWFsIG9ic2VydmVyLCB3aGljaCBhbHdheXMgcHJlZGljdHMgNTAvNTAgcmVzcG9uZGluZwogIG11dGF0ZShuZWdfbGxfaWRlYWxfb2JzID0gbmVnX2xvZ2xpa19sb2dpc3RpYygwLjUpKSAlPiUKICAjIFRpZHkgdXAKICBzZWxlY3QoCiAgICBzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwKICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkLAogICAgc3JfcHJlZmVycywgc3ViX2Nob2ljZSwKICAgIG5lZ19sbF9zciwgbmVnX2xsX2Jmc19iYWNrd2FyZCwgbmVnX2xsX2Jmc19mb3J3YXJkLCBuZWdfbGxfaWRlYWxfb2JzCiAgKQpgYGAKCkJlbG93LCB3ZSBzZWUgdGhhdCB0aGUgU1IsIGNvbXBhcmVkIHRvIHRoZSBvdGhlciBtb2RlbHMsIGlzIGRvaW5nIGEgYmV0dGVyIGpvYiBvZiBleHBsYWluaW5nIHN1YmplY3RzJyBjaG9pY2VzIG9uIGhlbGQtb3V0IHRyaWFscywgdGhvdWdoIHdlIGFnYWluIG5vdGUgdGhhdCB0aGUgbGlrZWxpaG9vZHMgZm9yIHRoZSBCRlMtYmFja3dhcmQgYW5kIGlkZWFsIG9ic2VydmVyIG1vZGVscyBhcmUgZm9yIGNvbXBsZXRlbHkgcmFuZG9tIHJlc3BvbmRpbmcuIFRoZSBkaWZmZXJlbnQgc3R1ZGllcyBoYXZlIGRpZmZlcmVudCBiYXNlbGluZSBsb2ctbGlrZWxpaG9vZHMgYmVjYXVzZSB0aGV5IGNvbnRhaW4gYSBkaWZmZXJlbnQgbnVtYmVyIG9mIHRyaWFscyAoaS5lLiwgaW4gU3R1ZGllcyAyLTMsIHdlJ3JlIHN1bW1pbmcgb3ZlciBib3RoIERheSAxIGFuZCBEYXkgMiBtZWFzdXJlbWVudHMpLgoKYGBge3IgcGxvdC1oZWxkb3V0LWxpa2VsaWhvb2RzfQpwbG90X2hlbGRvdXRfbGlrZWxpaG9vZHMgPC0gaGVsZG91dF9saWtlbGlob29kcyAlPiUKICAjIFN1bSBzbyB0aGF0IHdlIGdldCBvbmUgbmVnLWxvZ2xpayBwZXIgc3ViamVjdAogIGdyb3VwX2J5KHN0dWR5LCBzdWJfaWQsIG1lYXN1cmVtZW50X2lkKSAlPiUKICBzdW1tYXJpc2UoYWNyb3NzKHN0YXJ0c193aXRoKCJuZWdfbGxfIiksIHN1bSksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogICMgUHJlcCBmb3IgcGxvdHRpbmcKICBwaXZvdF9sb25nZXIoCiAgICBzdGFydHNfd2l0aCgibmVnX2xsXyIpLCBuYW1lc190byA9ICJtb2RlbCIsIHZhbHVlc190byA9ICJuZWdfbGwiCiAgKSAlPiUKICBtdXRhdGUobW9kZWwgPSBzdHJfcmVtb3ZlKG1vZGVsLCAibmVnX2xsXyIpLCBsb2dsaWsgPSAtbmVnX2xsKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWVhc3VyZW1lbnRfaWQsIHk9bG9nbGlrLCBjb2xvcj1tb2RlbCkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfd3JhcCh+c3R1ZHksIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgc3RhdF9zdW1tYXJ5KAogICAgZ2VvbSA9ICJjcm9zc2JhciIsIGZ1biA9IG1lZGlhbiwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuNzUpCiAgKSArCiAgZ2VvbV9wb2ludCgKICAgIGFscGhhID0gMC4yLAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXJkb2RnZSgKICAgICAgaml0dGVyLndpZHRoID0gMC4xLCBqaXR0ZXIuaGVpZ2h0ID0gMCwgZG9kZ2Uud2lkdGggPSAwLjc1LCBzZWVkID0gMQogICAgKSwKICAgIHNob3cubGVnZW5kID0gRkFMU0UKICApICsKICBzY2FsZV94X2Rpc2NyZXRlKAogICAgbmFtZSA9IE5VTEwsCiAgICBsYWJlbHMgPSBjKAogICAgICAiRDEiPSJCZWZvcmVcbnJlc3QiLAogICAgICAiRDIiPSJBZnRlclxub3Zlcm5pZ2h0XG5yZXN0IiwKICAgICAgIkQxYiI9IkFmdGVyXG5hd2FrZVxucmVzdCIKICAgICkKICApICsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJsb2ctbGlrZWxpaG9vZFxuKGdyZWF0ZXIgPSBiZXR0ZXIpIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgIG5hbWUgPSBOVUxMLAogICAgbGFiZWxzID0gYygKICAgICAgImJmc19iYWNrd2FyZCIgPSAiQkZTLWJhY2t3YXJkIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICJCRlMtZm9yd2FyZCIsCiAgICAgICJpZGVhbF9vYnMiID0gIklkZWFsIG9ic2VydmVyIiwKICAgICAgInNyIiA9ICJTdWNjZXNzb3IgUmVwLiIKICAgICksCiAgICB2YWx1ZXMgPSBjKAogICAgICAiYmZzX2JhY2t3YXJkIiA9ICIjYTZkYmEwIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICIjNWFhZTYxIiwKICAgICAgImlkZWFsX29icyIgPSAiIzFiNzgzNyIsCiAgICAgICJzciIgPSAiI2FmOGRjMyIKICAgICkKICApICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKwogIGdndGl0bGUoIkhlbGQtb3V0IHRyaWFsczogbG9nLWxpa2VsaWhvb2RzIikKCnBsb3RfaGVsZG91dF9saWtlbGlob29kcwoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwgImhlbGRvdXRfbGlrZWxpaG9vZHMucGRmIiksCiAgICBwbG90ID0gcGxvdF9oZWxkb3V0X2xpa2VsaWhvb2RzLAogICAgd2lkdGggPSA2LCBoZWlnaHQgPSA0LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgpBbHRob3VnaCB0aGUgbGlrZWxpaG9vZHMgZ2l2ZSB1cyBhIG5pY2UgcXVhbnRpdGF0aXZlIG1ldHJpYywgd2UgbWF5IGFsc28gYmUgaW50ZXJlc3RlZCBpbiBrbm93aW5nIGhvdyB3ZWxsIHRoZSBTUiBwcmVkaWN0cyBzdWJqZWN0cycgY2hvaWNlcyBqdXN0IGluIHRlcm1zIG9mIGFjY3VyYWN5LiBJbiB0aGUgYmVsb3cgYW5hbHlzaXMsIHdlJ2xsIHVzZSBtaXhlZC1lZmZlY3RzIGxvZ2lzdGljIHJlZ3Jlc3Npb24gdG8gdGVzdCB3aGV0aGVyIHN1YmplY3RzIHNpZ25pZmljYW50bHkgY2hvb3NlIHRoZSBTUi1wcmVmZXJyZWQgb3B0aW9uLiBOb3RlIHRoYXQgd2UncmUgcmVtb3ZpbmcgYWxsIG9mIHRoZSB0cmlhbHMgd2hlcmUgdGhlIFNSIGlzIGluZGlmZmVyZW50IHRvIHRoZSB0d28gb3B0aW9ucywgYXMgdGhvc2UgdHJpYWxzIGxlYWQgdXMgdG8gb3ZlcmVzdGltYXRlIHRoZSBtb2RlbCdzIHByZWRpY3RlZCBhY2N1cmFjeSAoaS5lLiwgYmVjYXVzZSB0aGUgc3ViamVjdCBpcyBhbHdheXMgcmlnaHQgb24gdGhvc2UgdHJpYWxzKS4gTm90ZSBhbHNvIHRoYXQgd2UncmUgaXRlcmF0aXZlbHkgcmUtcGFyYW1ldGVyaXppbmcgdGhlIG1vZGVsIHdpdGggYSBkaWZmZXJlbnQgcmVmZXJlbmNlIGNhdGVnb3J5IGVhY2ggdGltZSwgc28gdGhhdCB3ZSBjYW4gdGVzdCB3aGV0aGVyIG1vZGVsIGFjY3VyYWN5IGlzIHNpZ25pZmljYW50bHkgYWJvdmUgY2hhbmNlIGF0IGVhY2ggZGlzdGFuY2UuIEZpbmFsbHksIG5vdGUgdGhhdCBmb3IgdGhlIHB1cnBvc2Ugb2Ygc3RhdGlzdGljYWwgdGVzdGluZywgd2UncmUgbG9va2luZyBhdCB0aGUgdHdvIG1haW4gbWVhc3VyZW1lbnRzOiBiZWZvcmUgcmVzdCAoZGF5IDEpLCBhbmQgYWZ0ZXIgb3Zlcm5pZ2h0IHJlc3QgKGRheSAyKS4KCmBgYHtyIHRlc3QtaGVsZG91dC1hY2N1cmFjeX0Kc3RhdHNfaGVsZG91dF9hY2N1cmFjeV9kaXN0MiA8LSBoZWxkb3V0X2xpa2VsaWhvb2RzICU+JQogIGZpbHRlcihtZWFzdXJlbWVudF9pZCAlaW4lIGMoIkQxIiwgIkQyIikpICU+JQogIGZpbHRlcighaXMubmEoc3JfcHJlZmVycykpICU+JQogIG11dGF0ZSgKICAgIHBfc3ViX2Nob29zZXNfc3JfcHJlZmVyZW5jZSA9IHN1Yl9jaG9pY2UgPT0gc3JfcHJlZmVycywKICAgIHN1Yl9pZCA9IHN0cl9jKHN0dWR5LCAiLCBzIiwgc3ViX2lkKQogICkgJT4lCiAgZ2xtbVRNQigKICAgIHBfc3ViX2Nob29zZXNfc3JfcHJlZmVyZW5jZSB+IHNob3J0ZXN0X3BhdGggKwogICAgICAoMSB8IHN1Yl9pZCkgKyAoMSB8IHN0dWR5KSwKICAgIGZhbWlseSA9IGJpbm9taWFsLAogICAgZGF0YSA9IC4KICApCgpzdGF0c19oZWxkb3V0X2FjY3VyYWN5X2Rpc3QzIDwtIGhlbGRvdXRfbGlrZWxpaG9vZHMgJT4lCiAgZmlsdGVyKG1lYXN1cmVtZW50X2lkICVpbiUgYygiRDEiLCAiRDIiKSkgJT4lCiAgZmlsdGVyKCFpcy5uYShzcl9wcmVmZXJzKSkgJT4lCiAgbXV0YXRlKAogICAgcF9zdWJfY2hvb3Nlc19zcl9wcmVmZXJlbmNlID0gc3ViX2Nob2ljZSA9PSBzcl9wcmVmZXJzLAogICAgc3ViX2lkID0gc3RyX2Moc3R1ZHksICIsIHMiLCBzdWJfaWQpLAogICAgc2hvcnRlc3RfcGF0aCA9IGZjdF9yZWxldmVsKHNob3J0ZXN0X3BhdGgsICIzIikKICApICU+JQogIGdsbW1UTUIoCiAgICBwX3N1Yl9jaG9vc2VzX3NyX3ByZWZlcmVuY2UgfiBzaG9ydGVzdF9wYXRoICsKICAgICAgKDEgfCBzdWJfaWQpICsgKDEgfCBzdHVkeSksCiAgICBmYW1pbHkgPSBiaW5vbWlhbCwKICAgIGRhdGEgPSAuCiAgKQoKc3RhdHNfaGVsZG91dF9hY2N1cmFjeV9kaXN0NCA8LSBoZWxkb3V0X2xpa2VsaWhvb2RzICU+JQogIGZpbHRlcihtZWFzdXJlbWVudF9pZCAlaW4lIGMoIkQxIiwgIkQyIikpICU+JQogIGZpbHRlcighaXMubmEoc3JfcHJlZmVycykpICU+JQogIG11dGF0ZSgKICAgIHBfc3ViX2Nob29zZXNfc3JfcHJlZmVyZW5jZSA9IHN1Yl9jaG9pY2UgPT0gc3JfcHJlZmVycywKICAgIHN1Yl9pZCA9IHN0cl9jKHN0dWR5LCAiLCBzIiwgc3ViX2lkKSwKICAgIHNob3J0ZXN0X3BhdGggPSBmY3RfcmVsZXZlbChzaG9ydGVzdF9wYXRoLCAiNCIpCiAgKSAlPiUKICBnbG1tVE1CKAogICAgcF9zdWJfY2hvb3Nlc19zcl9wcmVmZXJlbmNlIH4gc2hvcnRlc3RfcGF0aCArCiAgICAgICgxIHwgc3ViX2lkKSArICgxIHwgc3R1ZHkpLAogICAgZmFtaWx5ID0gYmlub21pYWwsCiAgICBkYXRhID0gLgogICkKCm1hcF9kZnIoCiAgLnggPSBsaXN0KAogICAgImRpc3QyIiA9IHN0YXRzX2hlbGRvdXRfYWNjdXJhY3lfZGlzdDIsCiAgICAiZGlzdDMiID0gc3RhdHNfaGVsZG91dF9hY2N1cmFjeV9kaXN0MywKICAgICJkaXN0NCIgPSBzdGF0c19oZWxkb3V0X2FjY3VyYWN5X2Rpc3Q0CiAgKSwKICAuZiA9IH50aWR5KC54KSAlPiUgc2VsZWN0KC1jb21wb25lbnQpLAogIC5pZCA9ICJyZWZfY2F0IgopICU+JQogIGthYmxlX2N1c3RvbSgKICAgICJIZWxkLW91dCB0cmlhbHM6IFNSIG1vZGVsIGFjY3VyYWN5IiwKICAgIGdyb3VwaW5nX3ZhciA9IHJlZl9jYXQKICApCmBgYAoKSW4gdGhlIHBsb3QgYmVsb3csIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSdzIHF1aXRlIGEgYml0IG9mIHZhcmlhYmlsaXR5IGFjcm9zcyBzdWJqZWN0cyAoYmxhY2sgbGluZXMgYW5kIGRhdGFwb2ludHMpLCBidXQgYWxzbyB0aGF0IGF0IHRoZSBncm91cCBsZXZlbCwgdGhlIFNSIGFjaGlldmVzIGFib3ZlLWNoYW5jZSBhY2N1cmFjeSBmb3IgdGhlIGhlbGQtb3V0IGRpc3RhbmNlLTMgYW5kIGRpc3RhbmNlLTQgdHJpYWxzIChyZWQpLgoKYGBge3IgcGxvdC1oZWxkb3V0LWFjY3VyYWN5fQpwcmVkaWN0X2hlbGRvdXRfYWNjdXJhY3kgPC0gdGliYmxlKAogIHNob3J0ZXN0X3BhdGggPSBmYWN0b3IoMjo0KSwgc3ViX2lkID0gTkEsIHN0dWR5ID0gTkEKKSAlPiUKICBwcmVkaWN0X2dsbW1UTUIoc3RhdHNfaGVsZG91dF9hY2N1cmFjeV9kaXN0MikKCnBsb3RfaGVsZG91dF9hY2N1cmFjeSA8LSBoZWxkb3V0X2xpa2VsaWhvb2RzICU+JQogIGZpbHRlcihtZWFzdXJlbWVudF9pZCAlaW4lIGMoIkQxIiwgIkQyIikpICU+JQogIGZpbHRlcighaXMubmEoc3JfcHJlZmVycykpICU+JQogIGdyb3VwX2J5KHN0dWR5LCBzdWJfaWQsIHNob3J0ZXN0X3BhdGgpICU+JQogIHN1bW1hcmlzZSgKICAgIHBfc3ViX2Nob29zZXNfc3JfcHJlZmVyZW5jZSA9IG1lYW4oc3ViX2Nob2ljZSA9PSBzcl9wcmVmZXJzKSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIGdncGxvdChhZXMoeD1zaG9ydGVzdF9wYXRoLCB5PXBfc3ViX2Nob29zZXNfc3JfcHJlZmVyZW5jZSkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IGludGVyYWN0aW9uKHN0dWR5LCBzdWJfaWQpKSwgYWxwaGEgPSAwLjEpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArCiAgZ2VvbV9wb2ludHJhbmdlKAogICAgYWVzKHggPSBzaG9ydGVzdF9wYXRoLCB5ID0gZml0LCB5bWluID0gZml0IC0gc2UuZml0LCB5bWF4ID0gZml0ICsgc2UuZml0KSwKICAgIGRhdGEgPSBwcmVkaWN0X2hlbGRvdXRfYWNjdXJhY3ksIGluaGVyaXQuYWVzID0gRkFMU0UsCiAgICAjIGZhdHRlbiA9IDEsIHNpemUgPSAxLAogICAgbGluZXdpZHRoID0gMSwgY29sb3IgPSAicmVkIgogICkgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJTaG9ydGVzdCBwYXRoIGRpc3RhbmNlIikgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSAicChIdW1hbiBjaG9vc2VzIFNSLXByZWZlcnJlZCBTb3VyY2UpIiwKICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwKICAgIGJyZWFrcyA9IHNlcSgwLCAxLCAuMjUpLAogICAgZXhwYW5kID0gZXhwYW5zaW9uKG11bHQgPSBjKDAuMSwgMC4xNSkpCiAgKSArCiAgZ2d0aXRsZSgiSGVsZC1vdXQgdHJpYWxzOiBTUiBtb2RlbCBhY2N1cmFjeSIpCgpwbG90X2hlbGRvdXRfYWNjdXJhY3kKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJoZWxkb3V0X2FjY3VyYWN5LnBkZiIpLAogICAgcGxvdCA9IHBsb3RfaGVsZG91dF9hY2N1cmFjeSwKICAgIHdpZHRoID0gNCwgaGVpZ2h0ID0gNSwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKCiMgTWNGYWRkZW4ncyBwc2V1ZG8gUi1zcXVhcmVkCgpGaW5hbGx5LCBpdCBjYW4gYmUgdXNlZnVsIHRvIGdldCBhIHNlbnNlIGZvciBlYWNoIG1vZGVsJ3MgImdvb2RuZXNzLW9mLWZpdCIgYnkgY29tcHV0aW5nIHRoZSByYXRpbyBvZiBpdHMgbGlrZWxpaG9vZCB0byB0aGUgbGlrZWxpaG9vZCBvZiBhIG51bGwgbW9kZWwgKGkuZS4sIE1jRmFkZGVuJ3MgUi1zcXVhcmVkKS4gSGVyZSwgdGhlIG51bGwgbW9kZWwgaXMgYW4gYWdlbnQgdGhhdCBjaG9vc2VzIGNvbXBsZXRlbHkgYXQgcmFuZG9tIG9uIGV2ZXJ5IHRyaWFsLgoKYGBge3IgY2FsYy1tY2ZhZGRlbi1yMn0KbWNmYWRkZW5fcjIgPC0gYWljYyAlPiUKICBmaWx0ZXIobW9kZWwgIT0gInNyX2RlbHRhX3J1bGUiKSAlPiUKICBtdXRhdGUobG9nbGlrID0gLW5lZ19sb2dsaWspICU+JQogIHNlbGVjdChzdHVkeSwgc3ViX2lkLCBtZWFzdXJlbWVudF9pZCwgbW9kZWwsIG1vZGVsX2xvZ2xpayA9IGxvZ2xpaykgJT4lCiAgbXV0YXRlKAogICAgIyBOdWxsIG1vZGVsIGlzIGNoYW5jZS1sZXZlbCBjaG9pY2Ugb24gZXZlcnkgdHJpYWwsIHNvIHRoaXMgZW5kcyB1cCBiZWluZwogICAgIyBleGFjdGx5IGVxdWFsIHRvIHRoZSB1c3VhbCBmb3JtdWxhdGlvbgogICAgbnVsbF9sb2dsaWsgPSBuYXZfdHJpYWxzICU+JQogICAgICBmaWx0ZXIodHdvX2NvcnJlY3Rfb3B0aW9ucyA9PSBGQUxTRSkgJT4lCiAgICAgIG5yb3coKSAlPiUKICAgICAgey4gKiBsb2coMC41KX0KICApICU+JQogIG11dGF0ZShtY2ZhZGRlbl9yMiA9IDEgLSAobW9kZWxfbG9nbGlrIC8gbnVsbF9sb2dsaWspKQoKbWNmYWRkZW5fcjIgJT4lCiAgZ3JvdXBfYnkoc3R1ZHksIG1lYXN1cmVtZW50X2lkLCBtb2RlbCkgJT4lCiAgc3VtbWFyaXNlKE1lYW4gPSBtZWFuKG1jZmFkZGVuX3IyKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG1vZGVsLCB2YWx1ZXNfZnJvbSA9IE1lYW4pICU+JQogIGthYmxlX2N1c3RvbSgiTWVhbiBNY0ZhZGRlbidzIFIyIiwgZ3JvdXBpbmdfdmFyID0gc3R1ZHkpCgptY2ZhZGRlbl9yMiAlPiUKICBncm91cF9ieShzdHVkeSwgbWVhc3VyZW1lbnRfaWQsIG1vZGVsKSAlPiUKICBzdW1tYXJpc2UoTWVkaWFuID0gbWVkaWFuKG1jZmFkZGVuX3IyKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG1vZGVsLCB2YWx1ZXNfZnJvbSA9IE1lZGlhbikgJT4lCiAga2FibGVfY3VzdG9tKCJNZWRpYW4gTWNGYWRkZW4ncyBSMiIsIGdyb3VwaW5nX3ZhciA9IHN0dWR5KQpgYGAKCldlIHNlZSBpbiB0aGUgcGxvdCB0aGF0LCBnZW5lcmFsbHksIGFsbCBtb2RlbHMgYXJlIGRvaW5nIGJldHRlciB0aGFuIHRoZSBudWxsIG1vZGVsLCBhbmQgYWxzbyB0aGF0IHRoZSBTUiBjb25zaXN0ZW50bHkgc2VlbXMgdG8gaGF2ZSB0aGUgYmVzdCBsaWtlbGlob29kIHJhdGlvLgoKYGBge3IgcGxvdC1tY2ZhZGRlbi1yMn0KcGxvdF9tY2ZhZGRlbl9yMiA8LSBtY2ZhZGRlbl9yMiAlPiUKICBnZ3Bsb3QoYWVzKHg9bWVhc3VyZW1lbnRfaWQsIHk9bWNmYWRkZW5fcjIsIGNvbG9yPW1vZGVsKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zdHVkeSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX3BvaW50KAogICAgYWxwaGEgPSAwLjEsCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcmRvZGdlKAogICAgICBqaXR0ZXIud2lkdGggPSAwLjEsIGppdHRlci5oZWlnaHQgPSAwLCBkb2RnZS53aWR0aCA9IDAuNzUsIHNlZWQgPSAxCiAgICApLAogICAgc2hvdy5sZWdlbmQgPSBGQUxTRQogICkgKwogIHN0YXRfc3VtbWFyeSgKICAgIGdlb20gPSAiY3Jvc3NiYXIiLCBmdW4gPSBtZWRpYW4sIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjc1KQogICkgKwogIHNjYWxlX3hfZGlzY3JldGUoCiAgICBuYW1lID0gTlVMTCwKICAgIGxhYmVscyA9IGMoCiAgICAgICJEMSI9IkJlZm9yZVxucmVzdCIsCiAgICAgICJEMiI9IkFmdGVyXG5vdmVybmlnaHRcbnJlc3QiLAogICAgICAiRDFiIj0iQWZ0ZXJcbmF3YWtlXG5yZXN0IgogICAgKQogICkgKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gYnF1b3RlKH4iTWNGYWRkZW4ncyJ+Ul4yKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgIG5hbWUgPSBOVUxMLAogICAgbGFiZWxzID0gYygKICAgICAgImJmc19iYWNrd2FyZCIgPSAiQkZTLWJhY2t3YXJkIiwKICAgICAgImJmc19mb3J3YXJkIiA9ICJCRlMtZm9yd2FyZCIsCiAgICAgICJpZGVhbF9vYnMiID0gIklkZWFsIG9ic2VydmVyIiwKICAgICAgInNyX2FuYWx5dGljIiA9ICJTdWNjZXNzb3IgUmVwcmVzZW50YXRpb24iCiAgICApLAogICAgdmFsdWVzID0gYygKICAgICAgImJmc19iYWNrd2FyZCIgPSAiI2E2ZGJhMCIsCiAgICAgICJiZnNfZm9yd2FyZCIgPSAiIzVhYWU2MSIsCiAgICAgICJpZGVhbF9vYnMiID0gIiMxYjc4MzciLAogICAgICAic3JfYW5hbHl0aWMiID0gIiNhZjhkYzMiCiAgICApCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsKICBnZ3RpdGxlKCJNb2RlbCBnb29kbmVzcy1vZi1maXQiKQoKcGxvdF9tY2ZhZGRlbl9yMgoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwgIm1jZmFkZGVuX3IyLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfbWNmYWRkZW5fcjIsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDQsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKfQpgYGAKCgojIEZpZ3VyZXMgZm9yIHN1cHBsZW1lbnQKCmBgYHtyIHBsb3QtbW9kZWwtY29tcGFyaXNvbi1mb3Itc3VwcH0KcGxvdF9tb2RlbF9jb21wYXJpc29uX2Zvcl9zdXBwIDwtIHdyYXBfcGxvdHMoCiAgcGxvdF9ha2Fpa2VfZ3JvdXAsCiAgcGxvdF9iZXN0X2ZpdHRpbmdfbW9kZWxfcHJvcCwKICBwbG90X3B4cCwKICBndWlkZXMgPSAiY29sbGVjdCIsIG5yb3cgPSAxCikgKwogIHBsb3RfYW5ub3RhdGlvbigKICAgIHRpdGxlID0gIk1vZGVsIGNvbXBhcmlzb24iLAogICAgdGFnX2xldmVscyA9ICJBIiwgdGFnX3N1ZmZpeCA9ICIuIiwKICAgIHRoZW1lID0gdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgKSAmCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgpwbG90X21vZGVsX2NvbXBhcmlzb25fZm9yX3N1cHAKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgiZmlndXJlcyIsICJzdXBwX21vZGVsX2NvbXBhcmlzb24ucGRmIiksCiAgICBwbG90ID0gcGxvdF9tb2RlbF9jb21wYXJpc29uX2Zvcl9zdXBwLAogICAgd2lkdGggPSAxNiwgaGVpZ2h0ID0gNCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKYGBge3IgcGxvdC1wcGMtZm9yLXN1cHB9CnBsb3RfcHBjX2Zvcl9zdXBwIDwtIHdyYXBfcGxvdHMoCiAgcGxvdF9wcGNfZGF5MiArIGdndGl0bGUoIkFmdGVyIG92ZXJuaWdodCByZXN0IChTdHVkaWVzIDItMykiKSwKICBwbG90X3BwY19kYXkxYiArIGdndGl0bGUoIkFmdGVyIGF3YWtlIHJlc3QgKFN0dWR5IDMpIiksCiAgbmNvbCA9IDEKKSArCiAgcGxvdF9hbm5vdGF0aW9uKAogICAgdGl0bGUgPSAiUG9zdGVyaW9yIHByZWRpY3RpdmUgY2hlY2siLAogICAgdGFnX2xldmVscyA9ICJBIiwgdGFnX3N1ZmZpeCA9ICIuIiwKICAgIHRoZW1lID0gdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgKSAmCiAgc2NhbGVfeV9jb250aW51b3VzKAogICAgbmFtZSA9ICJBY2N1cmFjeSIsIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwKICAgIGxpbWl0cyA9IGMoLjI1LCAxKSwKICAgIGJyZWFrcyA9IHNlcSguMjUsIDEsIC4yNSkKICApCgpwbG90X3BwY19mb3Jfc3VwcAoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJmaWd1cmVzIiwgInN1cHBfcHBjLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfcHBjX2Zvcl9zdXBwLAogICAgd2lkdGggPSA4LCBoZWlnaHQgPSA2LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgpUaGVyZSBhcmUgc29tZSBwbG90cyB0aGF0IHdlIHdhbnQgdG8gdXNlIGFzLWlzLCBzbyB3ZSdsbCBzYXZlIGEgcmVkdW5kYW50IGNvcHkgaW4gdGhlIGBmaWd1cmVzYCBmb2xkZXIuCgpgYGB7ciBzYXZlLXJlZHVuZGFudC1jb3BpZXN9CmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgiZmlndXJlcyIsICJzdXBwX3BhcmFtX2VzdGltYXRlcy5wZGYiKSwKICAgIHBsb3QgPSBwbG90X3BhcmFtc19hbGwsCiAgICB3aWR0aCA9IDEyLCBoZWlnaHQgPSA2LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCiAgCiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJmaWd1cmVzIiwgInN1cHBfYWthaWtlX3dlaWdodHNfcGVyX3N1YmplY3QucGRmIiksCiAgICBwbG90ID0gcGxvdF9ha2Fpa2VfaW5kaXZpZHVhbCwKICAgIHdpZHRoID0gOCwgaGVpZ2h0ID0gMTAsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKICAKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoImZpZ3VyZXMiLCAic3VwcF9oZWxkb3V0X2xpa2VsaWhvb2RzLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfaGVsZG91dF9saWtlbGlob29kcywKICAgIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQogIAogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgiZmlndXJlcyIsICJzdXBwX2hlbGRvdXRfYWNjdXJhY3kucGRmIiksCiAgICBwbG90ID0gcGxvdF9oZWxkb3V0X2FjY3VyYWN5LAogICAgd2lkdGggPSA0LCBoZWlnaHQgPSA1LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCiAgCiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJmaWd1cmVzIiwgInN1cHBfbWNmYWRkZW5fcjIucGRmIiksCiAgICBwbG90ID0gcGxvdF9tY2ZhZGRlbl9yMiwKICAgIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoK